Merge "Fix VoicemailProviderTest for new columns" into nyc-mr2-dev am: f097acc609
am: 2b813a55e2
Change-Id: I655012e04a818f6351c56cebdbafe0a141176f3d
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 15b960d..b3d1c7e 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontakte"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kontakte-opgradering benodig meer geheue."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Gradeer berging vir kontakte op"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Raak om die opgradering te voltooi."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Tik om die opgradering te voltooi."</string>
<string name="default_directory" msgid="93961630309570294">"Kontakte"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Ander"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Stemboodskap van "</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index ad32b65..c16cd15 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"እውቅያዎች"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"የእውቅያዎች አልቅ ተጨማሪ ማህደረ ትውስታ ይፈልጋል"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"ለእውቅያዎች ማከማቻ በማሻሻል ላይ"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"አሻሽሉን ለማላቅ ይንኩ"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"ደረጃ ማሻሻሉን ለማጠናቀቅ ነካ ያድርጉ።"</string>
<string name="default_directory" msgid="93961630309570294">"እውቅያዎች"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"ሌላ"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"ከ....የድምፅ መልዕክት "</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 847813b..d142f75 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"جهات الاتصال"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"تتطلب ترقية جهات الاتصال مزيدًا من الذاكرة."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"ترقية وحدة التخزين لجهات الاتصال"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"المس لإكمال عملية الترقية."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"انقر لإكمال الترقية."</string>
<string name="default_directory" msgid="93961630309570294">"جهات الاتصال"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"غير ذلك"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"بريد صوتي من "</string>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az/strings.xml
similarity index 96%
rename from res/values-az-rAZ/strings.xml
rename to res/values-az/strings.xml
index 23c0885..9088a5c 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontaktlar"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kontakt təkmilləşdirməsi əlavə yaddaş tələb edir."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Kontakt üçün yaddaş təkmilləşdirilir."</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Təkmilləşdirməni tamamlamaq üçün toxunun."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Təkmilləşdirməni tamamlamaq üçün tıklayın."</string>
<string name="default_directory" msgid="93961630309570294">"Kontaktlar"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Digər"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Səsli mesaj göndərən: "</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index ae8b46c..b9427e4 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontakti"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Za ažuriranje kontakata potrebno je više memorije."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Nadograđivanje memorije za kontakte"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Dodirnite da biste dovršili nadogradnju."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Dodirnite da biste dovršili nadogradnju."</string>
<string name="default_directory" msgid="93961630309570294">"Kontakti"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Drugo"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Govorna pošta od "</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
new file mode 100644
index 0000000..0eeb7d4
--- /dev/null
+++ b/res/values-be/strings.xml
@@ -0,0 +1,35 @@
+<?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="sharedUserLabel" msgid="8024311725474286801">"Асноўныя праграмы для Android"</string>
+ <string name="app_label" msgid="3389954322874982620">"Сховішча кантактаў"</string>
+ <string name="provider_label" msgid="6012150850819899907">"Кантакты"</string>
+ <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Для абнаўлення кантактаў патрабуецца больш памяці."</string>
+ <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Каб абнавiць кантакты, патрабуецца больш памяцi"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Дакраніцеся, каб скончыць абнаўленне."</string>
+ <string name="default_directory" msgid="93961630309570294">"Кантакты"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Іншае"</string>
+ <string name="voicemail_from_column" msgid="435732568832121444">"Галасавое паведамленне ад "</string>
+ <string name="debug_dump_title" msgid="4916885724165570279">"Капiраваць базу дадзеных кантактаў"</string>
+ <string name="debug_dump_database_message" msgid="406438635002392290">"Вы збіраецеся 1) зрабіць копію базы дадзеных, якая ўключае ў сябе ўсе звесткi пра кантакты і званкi на ўнутранай памяці, і 2) адправiць яго па электроннай пошце. Не забудзьцеся выдаліць копію, як толькі вы паспяхова скапіруеце іх на прыладу ці атрымаеце па электроннай пошце."</string>
+ <string name="debug_dump_delete_button" msgid="7832879421132026435">"Выдаліць зараз"</string>
+ <string name="debug_dump_start_button" msgid="2837506913757600001">"Пачаць"</string>
+ <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Выберыце праграму для адпраўкі файла"</string>
+ <string name="debug_dump_email_subject" msgid="108188398416385976">"Далучаны кантакты Dd"</string>
+ <string name="debug_dump_email_body" msgid="4577749800871444318">"Далучана база дадзеных маiх кантактаў з усёй інфармацыяй. Працуйце з ёй уважліва."</string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index bf3f06b..fe07e03 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Контакти"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"За надстройването на контактите е необходима още памет."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Хранилището за контакти се надстройва"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Докоснете, за да завършите надстройването."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Докоснете, за да завършите надстройването."</string>
<string name="default_directory" msgid="93961630309570294">"Контакти"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Други"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Гласова поща от "</string>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn/strings.xml
similarity index 92%
rename from res/values-bn-rBD/strings.xml
rename to res/values-bn/strings.xml
index 2b8974d..dc803da 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn/strings.xml
@@ -21,15 +21,15 @@
<string name="provider_label" msgid="6012150850819899907">"পরিচিতিগুলি"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"পরিচিতিগুলি আপগ্রেড করার জন্য আরো সঞ্চয়স্থানের দরকার৷"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"পরিচিতিগুলির জন্য সঞ্চয়স্থান আপগ্রেড করা হচ্ছে"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"আপগ্রেড সম্পূর্ণ করতে স্পর্শ করুন৷"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"আপগ্রেড সম্পূর্ণ করতে আলতো চাপ দিন৷"</string>
<string name="default_directory" msgid="93961630309570294">"পরিচিতিগুলি"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"অন্যান্য"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"এর থেকে ভয়েসমেল "</string>
- <string name="debug_dump_title" msgid="4916885724165570279">"পরিচিতির ডেটাবেস অনুলিপি করুন"</string>
+ <string name="debug_dump_title" msgid="4916885724165570279">"পরিচিতির ডেটাবেস কপি করুন"</string>
<string name="debug_dump_database_message" msgid="406438635002392290">"আপনি ১) আপনার ডেটাবেসের সমস্ত পরিচিতি সংক্রান্ত তথ্য এবং অভ্যন্তরীণ সংগ্রহস্থলের সমস্ত কল লগ রয়েছে এমন একটি অনুলিপি, এবং ২) এটিকে ইমেল করতে চলেছেন৷ আপনি ডিভাইস থেকে সফলভাবে অনুলিপি করে এবং ইমেলটি পেয়ে যাবার সাথে সাথে অনুলিপিটি মুছে ফেলতে ভুলবেন না৷"</string>
<string name="debug_dump_delete_button" msgid="7832879421132026435">"এখনই মুছে দিন"</string>
<string name="debug_dump_start_button" msgid="2837506913757600001">"শুরু করুন"</string>
- <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"আপনার ফাইল পাঠানোর জন্য একটি প্রোগ্রাম চয়ন করুন"</string>
+ <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"আপনার ফাইল পাঠানোর জন্য একটি প্রোগ্রাম বেছে নিন"</string>
<string name="debug_dump_email_subject" msgid="108188398416385976">"পরিচিতির ডেটাবেস সংযুক্ত করা হয়েছে"</string>
<string name="debug_dump_email_body" msgid="4577749800871444318">"আমার সমস্ত পরিচিতির তথ্য সহ আমার পরিচিতির ডেটাবেস সংযুক্ত করা হয়েছে৷ সাবধানে ব্যবহার করবেন৷"</string>
</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-bs/strings.xml
similarity index 62%
copy from res/values-et-rEE/strings.xml
copy to res/values-bs/strings.xml
index 7676f73..c23e9ef 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-bs/strings.xml
@@ -16,20 +16,20 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="sharedUserLabel" msgid="8024311725474286801">"Androidi tuumrakendused"</string>
- <string name="app_label" msgid="3389954322874982620">"Kontaktiruum"</string>
- <string name="provider_label" msgid="6012150850819899907">"Kontaktid"</string>
- <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kontaktisikute uuendamiseks on vaja rohkem mälu"</string>
- <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Kontaktide salvestusruumi uuendamine"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Puudutage uuendamise lõpuleviimiseks."</string>
- <string name="default_directory" msgid="93961630309570294">"Kontaktid"</string>
- <string name="local_invisible_directory" msgid="705244318477396120">"Muu"</string>
- <string name="voicemail_from_column" msgid="435732568832121444">"Kõnepost kontaktilt "</string>
- <string name="debug_dump_title" msgid="4916885724165570279">"Kontaktide andmebaasi kopeerimine"</string>
- <string name="debug_dump_database_message" msgid="406438635002392290">"Soovite teha 1) sisemisse salvestusruumi koopia andmebaasist, mis sisaldab kogu kontaktidega seotud teavet ja kõikide kõnede logi ning 2) saata koopia meiliga. Kustutage koopia niipea, kui olete selle seadmest kopeerinud või meil on kohale jõudnud."</string>
- <string name="debug_dump_delete_button" msgid="7832879421132026435">"Kustuta kohe"</string>
- <string name="debug_dump_start_button" msgid="2837506913757600001">"Alusta"</string>
- <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Valige programm faili saatmiseks"</string>
- <string name="debug_dump_email_subject" msgid="108188398416385976">"Lisatud kontaktide andmebaas"</string>
- <string name="debug_dump_email_body" msgid="4577749800871444318">"Lisatud on minu kontaktide andmebaas, mis sisaldab kogu kontaktidega seotud teavet. Käsitsege ettevaatlikult."</string>
+ <string name="sharedUserLabel" msgid="8024311725474286801">"Android osnovne aplikacije"</string>
+ <string name="app_label" msgid="3389954322874982620">"Pohrana za kontakte"</string>
+ <string name="provider_label" msgid="6012150850819899907">"Kontakti"</string>
+ <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Za nadogradnju kontakata potrebno je više memorije."</string>
+ <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Nadogradnja pohrane za kontakte"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Dodirnite da završite nadogradnju."</string>
+ <string name="default_directory" msgid="93961630309570294">"Kontakti"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Ostalo"</string>
+ <string name="voicemail_from_column" msgid="435732568832121444">"Govorna pošta od "</string>
+ <string name="debug_dump_title" msgid="4916885724165570279">"Kopiraj bazu podataka kontakata"</string>
+ <string name="debug_dump_database_message" msgid="406438635002392290">"Upravo ćete 1) napraviti kopiju svoje baze podataka koja sadrži sve informacije o kontaktima i sve popise poziva u unutrašnjoj pohrani i 2) poslati tu kopiju e-poštom. Ne zaboravite izbrisati kopiju čim je uspješno kopirate s uređaja ili čim primite poruku e-pošte."</string>
+ <string name="debug_dump_delete_button" msgid="7832879421132026435">"Izbriši sada"</string>
+ <string name="debug_dump_start_button" msgid="2837506913757600001">"Počni"</string>
+ <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Izaberite program za slanje fajla"</string>
+ <string name="debug_dump_email_subject" msgid="108188398416385976">"Baza podataka kontakata je u prilogu"</string>
+ <string name="debug_dump_email_body" msgid="4577749800871444318">"U prilogu je moja baza podataka kontakata sa svim informacijama o kontaktima. Rukujte oprezno."</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 009aff9..9802e65 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contactes"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Per actualitzar els contactes cal tenir més memòria."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"S\'està actualitzant l\'emmagatzematge per als contactes"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Toca-ho per completar l\'actualització."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Toca per completar l\'actualització."</string>
<string name="default_directory" msgid="93961630309570294">"Contactes"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Altres"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Missatge de veu de "</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index e28b255..489b5b7 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontakty"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Upgrade kontaktů vyžaduje více paměti."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Probíhá upgrade úložiště kontaktů"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Dotykem dokončíte upgrade."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Klepnutím upgrade dokončíte."</string>
<string name="default_directory" msgid="93961630309570294">"Kontakty"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Jiné"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Hlasová zpráva od uživatele "</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index cb96932..fde3a2f 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -18,10 +18,10 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="sharedUserLabel" msgid="8024311725474286801">"Android-kerneapplikationer"</string>
<string name="app_label" msgid="3389954322874982620">"Lagring af kontakter"</string>
- <string name="provider_label" msgid="6012150850819899907">"Kontakter"</string>
+ <string name="provider_label" msgid="6012150850819899907">"Kontaktpersoner"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Opgradering af kontaktpersoner kræver mere hukommelse."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Opgraderer lagring af kontaktpersoner"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Tryk for at fuldføre opgraderingen."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Tryk for at gennemføre opgraderingen."</string>
<string name="default_directory" msgid="93961630309570294">"Kontaktpersoner"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Andre"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Telefonsvarerbesked fra "</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 4e310c2..4784c4a 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontakte"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kontakte-Upgrade erfordert mehr Speicher."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Speicher für Kontakte wird aktualisiert..."</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Zum Abschluss des Upgrades berühren"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Zum Abschließen der Aktualisierung tippen."</string>
<string name="default_directory" msgid="93961630309570294">"Kontakte"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Sonstige"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Mailbox-Nachricht von "</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index c7bbb89..faa014a 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -17,11 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="sharedUserLabel" msgid="8024311725474286801">"Βασικές εφαρμογές Android"</string>
- <string name="app_label" msgid="3389954322874982620">"Χώρος αποθήκευσης επαφών"</string>
+ <string name="app_label" msgid="3389954322874982620">"Αποθηκευτικός χώρος επαφών"</string>
<string name="provider_label" msgid="6012150850819899907">"Επαφές"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Απαιτείται περισσότερη μνήμη για την αναβάθμιση των επαφών."</string>
- <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Αναβάθμιση χώρου αποθήκευσης για επαφές"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Αγγίξτε για να ολοκληρώσετε την αναβάθμιση."</string>
+ <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Αναβάθμιση αποθηκευτικού χώρου για επαφές"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Πατήστε για να ολοκληρώσετε την αναβάθμιση."</string>
<string name="default_directory" msgid="93961630309570294">"Επαφές"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Άλλο"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Μήνυμα αυτόματου τηλεφωνητή από "</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 2279f29..c62b699 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contacts"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Contact upgrade needs more memory."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Upgrading storage for contacts"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Touch to complete the upgrade."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Tap to complete the upgrade."</string>
<string name="default_directory" msgid="93961630309570294">"Contacts"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Other"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Voicemail from "</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 2279f29..c62b699 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contacts"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Contact upgrade needs more memory."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Upgrading storage for contacts"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Touch to complete the upgrade."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Tap to complete the upgrade."</string>
<string name="default_directory" msgid="93961630309570294">"Contacts"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Other"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Voicemail from "</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 2279f29..c62b699 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contacts"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Contact upgrade needs more memory."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Upgrading storage for contacts"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Touch to complete the upgrade."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Tap to complete the upgrade."</string>
<string name="default_directory" msgid="93961630309570294">"Contacts"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Other"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Voicemail from "</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index fb9e6e1..603e6d6 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -21,13 +21,13 @@
<string name="provider_label" msgid="6012150850819899907">"Contactos"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"La actualización de contactos necesita más memoria."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Actualizando el espacio de almacenamiento de los contactos"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Toca para completar la actualización."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Presiona para completar la actualización."</string>
<string name="default_directory" msgid="93961630309570294">"Contactos"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Otro"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Mensaje de voz de "</string>
<string name="debug_dump_title" msgid="4916885724165570279">"Copiar base de datos de contactos"</string>
- <string name="debug_dump_database_message" msgid="406438635002392290">"Estás a punto de 1) copiar tu base datos, que incluye información de todos los contactos y el registro de todas las llamadas, en el almacenamiento interno; y de 2) enviar la copia por correo. Recuerda eliminar la copia inmediatamente después de guardarla fuera del dispositivo o de que se reciba el correo."</string>
- <string name="debug_dump_delete_button" msgid="7832879421132026435">"Eliminar ahora"</string>
+ <string name="debug_dump_database_message" msgid="406438635002392290">"Estás a punto de 1) copiar tu base datos, que incluye información de todos los contactos y el registro de todas las llamadas, en el almacenamiento interno; y de 2) enviar la copia por correo. Recuerda borrar la copia inmediatamente después de guardarla fuera del dispositivo o de que se reciba el correo."</string>
+ <string name="debug_dump_delete_button" msgid="7832879421132026435">"Borrar ahora"</string>
<string name="debug_dump_start_button" msgid="2837506913757600001">"Comenzar"</string>
<string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Elige un programa para enviar el archivo."</string>
<string name="debug_dump_email_subject" msgid="108188398416385976">"Base de datos de contactos adjunta"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 42f75e1..8157199 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contactos"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"La actualización de contactos necesita más memoria."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Actualizando el almacenamiento para contactos..."</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Toca para completar la actualización."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Toca para completar la actualización."</string>
<string name="default_directory" msgid="93961630309570294">"Contactos"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Otro"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Mensaje de voz de "</string>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et/strings.xml
similarity index 97%
rename from res/values-et-rEE/strings.xml
rename to res/values-et/strings.xml
index 7676f73..d5dbf95 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontaktid"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kontaktisikute uuendamiseks on vaja rohkem mälu"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Kontaktide salvestusruumi uuendamine"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Puudutage uuendamise lõpuleviimiseks."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Puudutage täiendamise lõpetamiseks."</string>
<string name="default_directory" msgid="93961630309570294">"Kontaktid"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Muu"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Kõnepost kontaktilt "</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu/strings.xml
similarity index 97%
rename from res/values-eu-rES/strings.xml
rename to res/values-eu/strings.xml
index 73da412..e9f97d2 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontaktuak"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Memoria gehiago behar da kontaktuak bertsio-berritzeko."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Kontaktuen biltegia bertsio-berritzea"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Bertsio-berritzea osatzeko, ukitu hau."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Sakatu bertsio-berritzea osatzeko."</string>
<string name="default_directory" msgid="93961630309570294">"Kontaktuak"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Beste bat"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Honen ahots-mezua: "</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index ae71e76..3266571 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -21,12 +21,12 @@
<string name="provider_label" msgid="6012150850819899907">"مخاطبین"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"برای ارتقای مخاطبین به حافظه بیشتری نیاز است."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"ارتقا حافظه برای مخاطبین"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"برای تکمیل ارتقا لمس کنید."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"برای تکمیل ارتقا ضربه بزنید."</string>
<string name="default_directory" msgid="93961630309570294">"مخاطبین"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"سایر موارد"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"پست صوتی از "</string>
<string name="debug_dump_title" msgid="4916885724165570279">"کپی پایگاه داده مخاطبین"</string>
- <string name="debug_dump_database_message" msgid="406438635002392290">"شما در شرف ۱) ایجاد یک کپی از پایگاه داده در حافظه داخلی هستید، این کپی حاوی همه اطلاعات مربوط به مخاطبین و همه گزارشات تماس است و همچنین میخواهید ۲) آنرا ایمیل کنید. به خاطر داشته باشید که به محض کپی کردن این نسخه در دستگاه یا دریافت ایمیل، آنرا حذف کنید."</string>
+ <string name="debug_dump_database_message" msgid="406438635002392290">"شما در شرف ۱) ایجاد یک کپی از پایگاه داده در حافظه داخلی هستید، این کپی حاوی همه اطلاعات مربوط به مخاطبین و همه گزارشات تماس است و همچنین میخواهید ۲) آنرا رایانامه کنید. به خاطر داشته باشید که به محض کپی کردن این نسخه در دستگاه یا دریافت رایانامه، آنرا حذف کنید."</string>
<string name="debug_dump_delete_button" msgid="7832879421132026435">"اکنون حذف شود"</string>
<string name="debug_dump_start_button" msgid="2837506913757600001">"شروع"</string>
<string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"یک برنامه را برای ارسال فایل خود انتخاب کنید"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 3608237..29ce010 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Yhteystiedot"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Yhteystietojen päivittämiseen tarvitaan enemmän muistia."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Päivitetään yhteystietojen tallennustilaa"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Suorita päivitys loppuun koskettamalla."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Suorita päivitys loppuun koskettamalla."</string>
<string name="default_directory" msgid="93961630309570294">"Yhteystiedot"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Muu"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Vastaajaviesti henkilöltä "</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 20836ac..cb7f6c8 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contacts"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"La mise à jour des contacts requiert plus de mémoire."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Mise à jour du stockage des contacts"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Appuyez pour terminer la mise à jour."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Touchez ici pour terminer la mise à niveau."</string>
<string name="default_directory" msgid="93961630309570294">"Contacts"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Autre"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Message vocal de "</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 3e06adc..8be5ef1 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contacts"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"La mise à jour des contacts requiert plus de mémoire."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Mise à jour du stockage des contacts"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Appuyez pour terminer la mise à jour."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Appuyez pour terminer la mise à jour."</string>
<string name="default_directory" msgid="93961630309570294">"Contacts"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Autre"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Message vocal de "</string>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl/strings.xml
similarity index 97%
rename from res/values-gl-rES/strings.xml
rename to res/values-gl/strings.xml
index ac85165..121853d 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contactos"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"A actualización dos contactos necesita máis memoria."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Actualizando almacenamento dos contactos"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Toca para completar a actualización."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Toca para completar a actualización."</string>
<string name="default_directory" msgid="93961630309570294">"Contactos"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Outro"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Correo de voz de "</string>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu/strings.xml
similarity index 95%
rename from res/values-gu-rIN/strings.xml
rename to res/values-gu/strings.xml
index 16cd70b..087ccc2 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu/strings.xml
@@ -16,12 +16,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="sharedUserLabel" msgid="8024311725474286801">"Android Core એપ્લિકેશનો"</string>
+ <string name="sharedUserLabel" msgid="8024311725474286801">"Android Core ઍપ્લિકેશનો"</string>
<string name="app_label" msgid="3389954322874982620">"સંપર્કો સ્ટોરેજ"</string>
<string name="provider_label" msgid="6012150850819899907">"સંપર્કો"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"સંપર્કો અપગ્રેડને વધુ મેમરીની જરૂર છે."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"સંપર્કો માટે સ્ટોરેજને અપગ્રેડ કરી રહ્યાં છે"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"અપગ્રેડ પૂર્ણ કરવા માટે ટચ કરો."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"અપગ્રેડ પૂર્ણ કરવા માટે ટૅપ કરો."</string>
<string name="default_directory" msgid="93961630309570294">"સંપર્કો"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"અન્ય"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"આમના તરફથી વૉઇસમેઇલ "</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 3bf42f6..5fa4021 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"संपर्क"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"संपर्क अपग्रेड के लिए अधिक मेमोरी की आवश्यकता होती है."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"संपर्कों के लिए मेमोरी अपग्रेड करना"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"अपग्रेड पूर्ण करने के लिए स्पर्श करें."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"अपग्रेड पूर्ण करने के लिए टैप करें."</string>
<string name="default_directory" msgid="93961630309570294">"संपर्क"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"अन्य"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"इनका ध्वनिमेल: "</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index e9159e7..17ee928 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontakti"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Za nadogradnju kontakata potrebno je više memorije."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Nadogradnja pohrane za kontakte"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Dodirnite da biste dovršili nadogradnju."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Dodirnite da biste dovršili nadogradnju."</string>
<string name="default_directory" msgid="93961630309570294">"Kontakti"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Drugo"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Govorna pošta od "</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index f7ea732..98e6f0f 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Címtár"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"A névjegyek frissítéséhez több memóriára van szükség."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Névjegyek tárolójának frissítése"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Érintse meg a frissítés befejezéséhez."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Koppintson a frissítés befejezéséhez."</string>
<string name="default_directory" msgid="93961630309570294">"Címtár"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Egyéb"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Hangüzenet tőle: "</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy/strings.xml
similarity index 96%
rename from res/values-hy-rAM/strings.xml
rename to res/values-hy/strings.xml
index 14475f7..2d31277 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Կոնտակտներ"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Կոնտակտների թարմացումը պահանջում է ավելի շատ հիշողություն:"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Կոնտակտների պահոցի թարմացում"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Հպեք` թարմացումն ավարտելու համար:"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Հպեք՝ նորացումն ավարտելու համար:"</string>
<string name="default_directory" msgid="93961630309570294">"Կոնտակտներ"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Այլ"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Ձայնային փոստ "</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 171db41..9a8a599 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontak"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Peningkatan versi kontak memerlukan lebih banyak memori."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Meningkatkan versi penyimpanan untuk kontak"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Sentuh untuk menyelesaikan peningkatan versi."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Ketuk untuk menyelesaikan peningkatan versi."</string>
<string name="default_directory" msgid="93961630309570294">"Kontak"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Lainnya"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Kotak pesan dari "</string>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is/strings.xml
similarity index 97%
rename from res/values-is-rIS/strings.xml
rename to res/values-is/strings.xml
index 7a6e04f..7e9feac 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Tengiliðir"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Uppfærsla tengiliða krefst meira minnis."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Uppfærsla á tengiliðageymslu"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Snertu til að ljúka uppfærslunni."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Ýttu til að ljúka uppfærslunni."</string>
<string name="default_directory" msgid="93961630309570294">"Tengiliðir"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Annað"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Talhólfsskilaboð frá "</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 1685b2e..4d52cea 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contatti"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"L\'upgrade dei contatti richiede più memoria."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Upgrade dell\'archivio dei contatti"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Tocca per completare l\'upgrade."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Tocca per completare l\'upgrade."</string>
<string name="default_directory" msgid="93961630309570294">"Contatti"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Altro"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Messaggio vocale da "</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 1cbd521..d9f3666 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"אנשי קשר"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"שדרוג אנשי הקשר מחייב זיכרון נוסף."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"משדרג את האחסון של אנשי קשר"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"גע כדי לבצע את השדרוג."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"הקש כדי להשלים את השדרוג."</string>
<string name="default_directory" msgid="93961630309570294">"אנשי קשר"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"אחר"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"הודעה קולית מאת "</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 4c95269..1756ab8 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"連絡先"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"連絡先のアップグレードに必要なメモリが不足しています。"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"連絡先用ストレージのアップグレード"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"アップグレードを完了するにはタップしてください。"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"アップグレードを完了するにはタップしてください"</string>
<string name="default_directory" msgid="93961630309570294">"連絡先"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"その他"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"受信ボイスメール: "</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka/strings.xml
similarity index 98%
rename from res/values-ka-rGE/strings.xml
rename to res/values-ka/strings.xml
index 3567fd7..2859997 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"კონტაქტები"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"კონტაქტების ახალ ვერსიაზე გადასვლას ესაჭიროება მეტი მეხსიერება."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"კონტაქტების მეხსიერების ახალ ვერსიაზე გადასვლა"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"შეეხეთ ახალ ვერსიაზე გადასვლის დასასრულებლად."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"შეეხეთ ახალ ვერსიაზე გადასვლის დასასრულებლად."</string>
<string name="default_directory" msgid="93961630309570294">"კონტაქტები"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"სხვა"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"ხმოვანი ფოსტა "</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk/strings.xml
similarity index 97%
rename from res/values-kk-rKZ/strings.xml
rename to res/values-kk/strings.xml
index 5d4b6f2..b8cd167 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Контактілер"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Контактілерді жаңарту көбірек жад кеңістігін қажет етеді."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Контактілер үшін жадты жаңарту"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Жаңартуды аяқтау үшін түрту."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Жаңартуды аяқтау үшін түртіңіз."</string>
<string name="default_directory" msgid="93961630309570294">"Контактілер"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Басқа"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Келесі нөмірден келген дауыс-хабар "</string>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km/strings.xml
similarity index 96%
rename from res/values-km-rKH/strings.xml
rename to res/values-km/strings.xml
index c754053..2a2b50a 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"ទំនាក់ទំនង"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"ការធ្វើបច្ចុប្បន្នភាពទំនាក់ទំនងត្រូវការអង្គចងចាំច្រើន"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"ធ្វើបច្ចុប្បន្នភាពឧបករណ៍ផ្ទុកទំនាក់ទំនង"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"ប៉ះដើម្បីបញ្ចប់ការធ្វើបច្ចុប្បន្នភាព។"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"ប៉ះដើម្បីបញ្ចប់ការអាប់គ្រេត"</string>
<string name="default_directory" msgid="93961630309570294">"ទំនាក់ទំនង"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"ផ្សេងៗ"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"សារជាសំឡេងពី "</string>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn/strings.xml
similarity index 95%
rename from res/values-kn-rIN/strings.xml
rename to res/values-kn/strings.xml
index 5a67519..0e37eae 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn/strings.xml
@@ -21,13 +21,13 @@
<string name="provider_label" msgid="6012150850819899907">"ಸಂಪರ್ಕಗಳು"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"ಸಂಪರ್ಕಗಳ ಅಪ್ಗ್ರೇಡ್ಗೆ ಹೆಚ್ಚಿನ ಸ್ಮರಣೆಯ ಅಗತ್ಯವಿದೆ."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"ಸಂಪರ್ಕಗಳಿಗಾಗಿ ಸಂಗ್ರಹಣೆಯನ್ನು ಅಪ್ಗ್ರೇಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"ಅಪ್ಗ್ರೇಡ್ ಪೂರ್ಣಗೊಳಿಸಲು ಸ್ಪರ್ಶಿಸಿ."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"ಅಪ್ಗ್ರೇಡ್ ಪೂರ್ಣಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="default_directory" msgid="93961630309570294">"ಸಂಪರ್ಕಗಳು"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"ಇತರೆ"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"ಇದರಿಂದ ಧ್ವನಿಮೇಲ್ "</string>
<string name="debug_dump_title" msgid="4916885724165570279">"ಸಂಪರ್ಕಗಳ ಡೇಟಾಬೇಸ್ ನಕಲಿಸಿ"</string>
<string name="debug_dump_database_message" msgid="406438635002392290">"ನೀವು 1) ಎಲ್ಲಾ ಸಂಪರ್ಕಗಳ ಸಂಬಂಧಿಸಿದ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿರುವ ನಿಮ್ಮ ಡೇಟಾಬೇಸ್ ನಕಲು ಮಾಡಲು ಮತ್ತು ಆಂತರಿಕ ಸಂಗ್ರಹಣೆಗೆ ಎಲ್ಲ ಕರೆಯ ಲಾಗ್ ಮಾಡಲು ಮತ್ತು 2) ಇಮೇಲ್ ಮಾಡಲಿರುವಿರಿ. ನೀವು ಸಾಧನವನ್ನು ಯಶಸ್ವಿಯಾಗಿ ನಕಲು ಮಾಡಿದ ಬಳಿಕ ಅಥವಾ ಇಮೇಲ್ ಸ್ವೀಕರಿಸಿದ ಕೂಡಲೇ ನಕಲು ಅಳಿಸುವುದನ್ನು ನೆನಪಿನಲ್ಲಿರಿಸಿಕೊಳ್ಳಿ."</string>
- <string name="debug_dump_delete_button" msgid="7832879421132026435">"ಈಗ ಅಳಿಸು"</string>
+ <string name="debug_dump_delete_button" msgid="7832879421132026435">"ಈಗ ಅಳಿಸಿ"</string>
<string name="debug_dump_start_button" msgid="2837506913757600001">"ಪ್ರಾರಂಭಿಸು"</string>
<string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"ನಿಮ್ಮ ಫೈಲ್ ಕಳುಹಿಸಲು ಪ್ರೋಗ್ರಾಂ ಒಂದನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
<string name="debug_dump_email_subject" msgid="108188398416385976">"ಸಂಪರ್ಕಗಳ Db ಲಗತ್ತಿಸಲಾಗಿದೆ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index ad1cb75..6e76912 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"주소록"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"주소록을 업그레이드하려면 메모리가 더 필요합니다."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"주소록을 위한 저장소 업그레이드 중"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"업그레이드를 완료하려면 터치하세요."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"업그레이드를 완료하려면 탭하세요."</string>
<string name="default_directory" msgid="93961630309570294">"주소록"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"기타"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"음성사서함 발신자 "</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky/strings.xml
similarity index 84%
rename from res/values-ky-rKG/strings.xml
rename to res/values-ky/strings.xml
index a886e2d..d8251c5 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky/strings.xml
@@ -17,17 +17,13 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="sharedUserLabel" msgid="8024311725474286801">"Android Core колдонмолору"</string>
- <!-- no translation found for app_label (3389954322874982620) -->
- <skip />
- <!-- no translation found for provider_label (6012150850819899907) -->
- <skip />
+ <string name="app_label" msgid="3389954322874982620">"Байланыштар сактагычы"</string>
+ <string name="provider_label" msgid="6012150850819899907">"Байланыштар"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Байланыштарды жаңыртууга көбүрөөк орун керек."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Байланыштар үчүн сактагыч жаңыртылууда"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Жаңыртууну аягына чыгарыш үчүн тийиңиз."</string>
- <!-- no translation found for default_directory (93961630309570294) -->
- <skip />
- <!-- no translation found for local_invisible_directory (705244318477396120) -->
- <skip />
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Жаңыртууну аягына чыгарыш үчүн таптап коюңуз."</string>
+ <string name="default_directory" msgid="93961630309570294">"Байланыштар"</string>
+ <string name="local_invisible_directory" msgid="705244318477396120">"Башка"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Келген үнкат "</string>
<string name="debug_dump_title" msgid="4916885724165570279">"Байланыштар корун көчүрүү"</string>
<string name="debug_dump_database_message" msgid="406438635002392290">"Сиз буларды кылайын деп жатасыз: 1) Ичинде бардык байланыштарга тийиштүү маалыматтар жана чалуу тизмелери бар берилиштер корун сырткы сактагычка көчүрмөлөө, 2) жана аны эмейлге жөнөтүү. Көчүрмөнү, түзмөктөн ийгиликтүү көчүрүп же эмейлден алаарыңыз менен, жок кылууну унутпаңыз."</string>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo/strings.xml
similarity index 95%
rename from res/values-lo-rLA/strings.xml
rename to res/values-lo/strings.xml
index 35ce0c9..b75c816 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo/strings.xml
@@ -21,8 +21,8 @@
<string name="provider_label" msgid="6012150850819899907">"ລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"ການອັບເກຣດລາຍຊື່ຜູ່ຕິດຕໍ່ ຈະຕ້ອງໃຊ້ໜ່ວຍຄວາມຈຳເພີ່ມຕື່ມ."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"ອັບເກຣດບ່ອນຈັດເກັບຂໍ້ມູນລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"ແຕະເພື່ອສິ້ນສຸດຂັ້ນຕອນການອັບເກຣດ."</string>
- <string name="default_directory" msgid="93961630309570294">"ລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"ແຕະເພື່ອສຳເລັດການອັບເກຣດ."</string>
+ <string name="default_directory" msgid="93961630309570294">"ລາຍຊື່ຜູ້ຕິດຕໍ່"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"ອື່ນໆ"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"ຂໍ້ຄວາມສຽງຈາກ "</string>
<string name="debug_dump_title" msgid="4916885724165570279">"ສຳເນົາຖານຂໍ້ມູນລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index eaa968c..2681de8 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Adresinė"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Norint naujovinti kontaktus, reikia daugiau atminties."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Naujovinama kontaktų atmintinė"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Palieskite, kad baigtumėte naujovinti."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Palieskite, kad būtų užbaigtas naujovinimas"</string>
<string name="default_directory" msgid="93961630309570294">"Kontaktai"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Kita"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Balso pašto pranešimas nuo "</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 9227f58..4593240 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontaktpersonas"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Lai jauninātu kontaktpersonas, nepieciešams vairāk vietas atmiņā."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Notiek kontaktpersonu krātuves jaunināšana"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Pieskarieties, lai pabeigtu jaunināšanu."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Pieskarieties, lai pabeigtu jaunināšanu."</string>
<string name="default_directory" msgid="93961630309570294">"Kontaktpersonas"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Cits"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Balss pasta ziņojums no "</string>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk/strings.xml
similarity index 97%
rename from res/values-mk-rMK/strings.xml
rename to res/values-mk/strings.xml
index 1c3a4cc..dc1a07a 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Контакти"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"За надградбата на контакти е потребно повеќе меморија."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Надградување меморија за контакти"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Допри за да заврши надградбата."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Допри за да заврши надградбата."</string>
<string name="default_directory" msgid="93961630309570294">"Контакти"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Друг"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Говорна пошта од "</string>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml/strings.xml
similarity index 96%
rename from res/values-ml-rIN/strings.xml
rename to res/values-ml/strings.xml
index a3aa6d1..dc2abdf 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"വിലാസങ്ങൾ"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"കോൺടാക്റ്റുകൾ അപ്ഗ്രേഡുചെയ്യാൻ കൂടുതൽ മെമ്മറി ആവശ്യമാണ്."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"കോൺടാക്റ്റുകൾക്കായുള്ള സ്റ്റോറേജ് അപ്ഗ്രേഡുചെയ്യുന്നു"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"അപ്ഗ്രേഡ് പൂർത്തിയാക്കാൻ സ്പർശിക്കുക."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"അപ്ഗ്രേഡ് പൂർത്തിയാക്കാൻ ടാപ്പുചെയ്യുക."</string>
<string name="default_directory" msgid="93961630309570294">"വിലാസങ്ങൾ"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"മറ്റുള്ളവ"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"ഈ നമ്പറിൽ നിന്നുള്ള വോയ്സ്മെയിൽ "</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn/strings.xml
similarity index 96%
rename from res/values-mn-rMN/strings.xml
rename to res/values-mn/strings.xml
index 8b7ff23..b15c2d1 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Харилцагчид"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Харилцагчдын сайжруулалт хийхэд илүү их санах ой шаардлагатай."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Харилцагчдад зориулсан санг сайжруулж байна"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Сайжруулалтыг дуусгахын тулд хүрнэ үү."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Шинэчлэлтийг дуусгахын тулд дарна уу."</string>
<string name="default_directory" msgid="93961630309570294">"Харилцагчид"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Бусад"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Дуут шуудан илгээгч "</string>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr/strings.xml
similarity index 95%
rename from res/values-mr-rIN/strings.xml
rename to res/values-mr/strings.xml
index 778f6fd..7e6d605 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"संपर्क"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"संपर्क श्रेणीसुधारित करण्यास अधिक मेमरी आवश्यक आहे."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"संपर्कांसाठी संचयन श्रेणीसुधारित करीत आहे"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"श्रेणीसुधारित करणे पूर्ण करण्यासाठी स्पर्श करा."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"श्रेणीसुधारणा पूर्ण करण्यासाठी टॅप करा."</string>
<string name="default_directory" msgid="93961630309570294">"संपर्क"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"इतर"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"कडून व्हॉईसमेल "</string>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms/strings.xml
similarity index 97%
rename from res/values-ms-rMY/strings.xml
rename to res/values-ms/strings.xml
index 10b3e5a..b638974 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kenalan"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Peningkatan kenalan memerlukan lebih banyak memori."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Meningkatkan storan untuk kenalan"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Sentuh untuk menyelesaikan peningkatan."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Ketik untuk menyelesaikan peningkatan."</string>
<string name="default_directory" msgid="93961630309570294">"Kenalan"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Lain-lain"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Mel suara daripada "</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my/strings.xml
similarity index 94%
rename from res/values-my-rMM/strings.xml
rename to res/values-my/strings.xml
index 7745ea9..a8f2cb9 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my/strings.xml
@@ -16,12 +16,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="sharedUserLabel" msgid="8024311725474286801">"Androidပင်မ အပ်ပလီကေးရှင်းများ"</string>
+ <string name="sharedUserLabel" msgid="8024311725474286801">"Androidပင်မ အက်ပ်များ"</string>
<string name="app_label" msgid="3389954322874982620">"လိပ်စာများသိမ်းဆည်းသောအပ်ပလီကေးရှင်း"</string>
<string name="provider_label" msgid="6012150850819899907">"အဆက်အသွယ်များ"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"အဆက်အသွယ်များ အဆင့်မြှင့်ခြင်းအတွက် မှတ်ဉာဏ်စွမ်းရည်ပိုလိုအပ်သည်"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"အဆယ်အသွယ်များ သိမ်းဆည်းရန် နေရာ အဆင့်မြှင့်ခြင်း"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"အဆင့်မြှင့်ခြင်း ပြီးဆုံးရန် ထိကိုင်ပါ"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"အဆင့်မြှင့်တင်ခြင်းပြီးမြောက်ရန် တို့ပါ။"</string>
<string name="default_directory" msgid="93961630309570294">"အဆက်အသွယ်များ"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"တစ်ခြား"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"မှ အသံစာ "</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 1cf97fd..d2e13dd 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontakter"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Du har ikke nok minne til å oppgradere kontaktene."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Oppgraderer lagring for kontakter"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Trykk for å fullføre oppgraderingen."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Trykk for å fullføre oppgraderingen."</string>
<string name="default_directory" msgid="93961630309570294">"Kontakter"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Annet"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Talemelding fra "</string>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne/strings.xml
similarity index 96%
rename from res/values-ne-rNP/strings.xml
rename to res/values-ne/strings.xml
index 0fd3913..e813957 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"सम्पर्कहरू"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"सम्पर्क अद्यावधिकका लागि अझै धेरै मेमोरी चाहिन्छ।"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"सम्पर्कका लागि भणडारण अद्यावधिक गर्दै"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"अद्यावधिक कार्य पुरा गर्न छुनुहोस्।"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"स्तरवृद्धि पूरा गर्न ट्याप गर्नुहोस्।"</string>
<string name="default_directory" msgid="93961630309570294">"सम्पर्कहरू"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"अन्य"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"बाट भ्वाइसमेल "</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 0cfeff5..f2bcb5a 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contacten"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Voor het bijwerken van contacten is meer geheugen nodig."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Opslag voor contacten bijwerken"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Raak aan om de upgrade te voltooien."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Tik om de upgrade te voltooien."</string>
<string name="default_directory" msgid="93961630309570294">"Contacten"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Overig"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Voicemail van "</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa/strings.xml
similarity index 96%
rename from res/values-pa-rIN/strings.xml
rename to res/values-pa/strings.xml
index cfc32d1..12a5c6e 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"ਸੰਪਰਕ"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"ਸੰਪਰਕ ਅਪਗ੍ਰੇਡ ਲਈ ਵੱਧ ਮੈਮਰੀ ਦੀ ਲੋੜ ਹੈ।"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"ਸੰਪਰਕਾਂ ਲਈ ਸਟੋਰੇਜ ਅਪਗ੍ਰੇਡ ਕਰ ਰਿਹਾ ਹੈ"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"ਅਪਗ੍ਰੇਡ ਪੂਰਾ ਕਰਨ ਲਈ ਛੋਹਵੋ।"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"ਅੱਪਗ੍ਰੇਡ ਮੁਕੰਮਲ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="default_directory" msgid="93961630309570294">"ਸੰਪਰਕ"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"ਹੋਰ"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"ਇਸ ਤੋਂ ਵੌਇਸਮੇਲ "</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 0934542..232b4b7 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontakty"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Uaktualnienie kontaktów wymaga więcej pamięci."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Pamięć na uaktualnienie kontaktów"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Dotknij, aby dokończyć uaktualnianie."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Kliknij, by ukończyć uaktualnienie."</string>
<string name="default_directory" msgid="93961630309570294">"Kontakty"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Inne"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Poczta głosowa od "</string>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
index 6906957..3ffd077 100644
--- a/res/values-pt-rBR/strings.xml
+++ b/res/values-pt-rBR/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contatos"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"A atualização de contatos precisa de mais memória."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Atualização do armazenamento para contatos"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Toque para concluir a atualização."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Toque para concluir o upgrade."</string>
<string name="default_directory" msgid="93961630309570294">"Contatos"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Outros"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Correio de voz de "</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index fcea4e0..beaa2c7 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contactos"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"A atualização de contactos necessita de mais memória."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"A atualizar armazenamento de contactos"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Toque para concluir a atualização."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Toque para concluir a atualização."</string>
<string name="default_directory" msgid="93961630309570294">"Contactos"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Outro"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Correio de voz de "</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 6906957..3ffd077 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Contatos"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"A atualização de contatos precisa de mais memória."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Atualização do armazenamento para contatos"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Toque para concluir a atualização."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Toque para concluir o upgrade."</string>
<string name="default_directory" msgid="93961630309570294">"Contatos"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Outros"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Correio de voz de "</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index a8814cc..3e7cb60 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -20,16 +20,16 @@
<string name="app_label" msgid="3389954322874982620">"Stocarea datelor din Agendă"</string>
<string name="provider_label" msgid="6012150850819899907">"Agendă"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Actualizarea agendei necesită mai multă memorie."</string>
- <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Măriți spaţiul de stocare pentru Agendă"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Atingeți pentru a finaliza actualizarea."</string>
+ <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Măriți spațiul de stocare pentru Agendă"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Atingeți pentru a finaliza upgrade-ul."</string>
<string name="default_directory" msgid="93961630309570294">"Agendă"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Altul"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Mesaj vocal de la "</string>
<string name="debug_dump_title" msgid="4916885724165570279">"Copiați baza de date a agendei"</string>
- <string name="debug_dump_database_message" msgid="406438635002392290">"Sunteți pe cale 1) să faceţi o copie, pe stocarea internă, a bazei dvs. de date care include toate informațiile referitoare la agendă și întregul jurnal de apeluri și 2) să trimiteți această copie prin e-mail. Nu uitați să ștergeți această copie după ce ați copiat-o de pe dispozitiv sau după ce a fost primit e-mailul."</string>
- <string name="debug_dump_delete_button" msgid="7832879421132026435">"Ștergeţi acum"</string>
- <string name="debug_dump_start_button" msgid="2837506913757600001">"Porniţi"</string>
- <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Alegeți un program pentru a trimite fişierul"</string>
+ <string name="debug_dump_database_message" msgid="406438635002392290">"Sunteți pe cale 1) să faceți o copie, pe stocarea internă, a bazei dvs. de date care include toate informațiile referitoare la agendă și întregul jurnal de apeluri și 2) să trimiteți această copie prin e-mail. Nu uitați să ștergeți această copie după ce ați copiat-o de pe dispozitiv sau după ce a fost primit e-mailul."</string>
+ <string name="debug_dump_delete_button" msgid="7832879421132026435">"Ștergeți acum"</string>
+ <string name="debug_dump_start_button" msgid="2837506913757600001">"Porniți"</string>
+ <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Alegeți un program pentru a trimite fișierul"</string>
<string name="debug_dump_email_subject" msgid="108188398416385976">"Atașată baza de date a agendei"</string>
- <string name="debug_dump_email_body" msgid="4577749800871444318">"Vă trimit atașată baza de date cu toate informațiile din agenda mea. Vă rog să o gestionați cu atenţie."</string>
+ <string name="debug_dump_email_body" msgid="4577749800871444318">"Vă trimit atașată baza de date cu toate informațiile din agenda mea. Vă rog să o gestionați cu atenție."</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index e3fe19c..7526328 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Контакты"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Для обновления контактов нужно больше памяти."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Недостаточно места для обновления контактов"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Нажмите, чтобы завершить обновление."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Нажмите, чтобы завершить обновление."</string>
<string name="default_directory" msgid="93961630309570294">"Контакты"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Другое"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Голосовое сообщение от абонента "</string>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si/strings.xml
similarity index 95%
rename from res/values-si-rLK/strings.xml
rename to res/values-si/strings.xml
index e5b50a3..906978b 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"සම්බන්ධතා"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"සම්බන්ධතා උත්ශ්රේණි කිරීමට තව මතකය අවශ්යයි."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"සම්බන්ධතා සඳහා ආචයනය උත්ශ්රේණි කරමින්"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"උත්ශ්රේණි කිරීම සම්පූර්ණ කිරීමට ස්පර්ශ කරන්න."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"උත්ශ්රේණි කිරීම සම්පූර්ණ කිරීමට තට්ටු කරන්න."</string>
<string name="default_directory" msgid="93961630309570294">"සම්බන්ධතා"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"වෙනත්"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"හඬ තැපෑල ලැබෙන්නේ "</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index a1e3bf2..abfa1c5 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontakty"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Inovácia kontaktov vyžaduje viac pamäte."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Prebieha inovácia úložiska pre kontakty"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Dotykom na túto možnosť dokončíte inováciu."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Klepnutím dokončite inováciu."</string>
<string name="default_directory" msgid="93961630309570294">"Kontakty"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Iné"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Hlasová správa od "</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index cd34f2d..fe5ea9e 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Stiki"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Za nadgradnjo stikov je potrebno več pomnilnika"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Nadgradnja shrambe za stike"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Dotaknite se, da končate nadgradnjo."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Dotaknite se, če želite dokončati nadgradnjo."</string>
<string name="default_directory" msgid="93961630309570294">"Stiki"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Drugo"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Govorna pošta s številke "</string>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq/strings.xml
similarity index 97%
rename from res/values-sq-rAL/strings.xml
rename to res/values-sq/strings.xml
index f3c7317..37830ff 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontaktet"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Përmirësimi i kontakteve ka nevojë për më shumë memorie."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Po përmirëson hapësirën ruajtëse për kontaktet"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Prek për të përfunduar përmirësimin."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Trokit për të përfunduar përmirësimin."</string>
<string name="default_directory" msgid="93961630309570294">"Kontaktet"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Tjetër"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Postë zanore nga "</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 97b65d5..8230513 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Контакти"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"За ажурирање контаката потребно је више меморије."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Надограђивање меморије за контакте"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Додирните да бисте довршили надоградњу."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Додирните да бисте довршили надоградњу."</string>
<string name="default_directory" msgid="93961630309570294">"Контакти"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Другo"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Говорна пошта од "</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 12240a4..e1d2023 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontakter"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kontaktuppgradering kräver mer minne."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Lagringsutrymmet för kontakter uppgraderas"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Tryck om du vill slutföra uppgraderingen."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Slutför uppgraderingen genom att trycka här."</string>
<string name="default_directory" msgid="93961630309570294">"Kontakter"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Övrigt"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Röstmeddelande från "</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 5249091..920511a 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Anwani"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kupandishwa gredi kwa anwani kunahitaji kumbukumbu zaidi."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Inapandisha gredi hifadhi ya anwani"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Gusa ili kukamilisha kupandisha gredi."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Gonga ili ukamilishe kusasisha anwani."</string>
<string name="default_directory" msgid="93961630309570294">"Anwani"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Nyingineyo"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Barua ya sauti kutoka "</string>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta/strings.xml
similarity index 96%
rename from res/values-ta-rIN/strings.xml
rename to res/values-ta/strings.xml
index 22015e6..1116f19 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"தொடர்புகள்"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"தொடர்புகளின் மேம்படுத்தலுக்கு கூடுதல் நினைவகம் தேவை."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"தொடர்புகளுக்காகச் சேமிப்பிடத்தை மேம்படுத்துகிறது"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"மேம்படுத்தலை முடிக்க தொடவும்."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"மேம்படுத்தலை முடிக்க, தட்டவும்."</string>
<string name="default_directory" msgid="93961630309570294">"தொடர்புகள்"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"மற்றவை"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"இவரிடமிருந்து குரலஞ்சல் "</string>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te/strings.xml
similarity index 96%
rename from res/values-te-rIN/strings.xml
rename to res/values-te/strings.xml
index c385874..d6cbf41 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"పరిచయాలు"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"పరిచయాల అప్గ్రేడ్కు మరింత మెమరీ అవసరం."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"పరిచయాల కోసం నిల్వను అప్గ్రేడ్ చేస్తోంది"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"అప్గ్రేడ్ను పూర్తి చేయడానికి తాకండి."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"అప్గ్రేడ్ను పూర్తి చేయడానికి నొక్కండి."</string>
<string name="default_directory" msgid="93961630309570294">"పరిచయాలు"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"ఇతరం"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"దీని నుండి వాయిస్ మెయిల్ "</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index da135d4..e3b384d 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"สมุดโทรศัพท์"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"ต้องใช้หน่วยความจำเพิ่มเพื่ออัปเกรดสมุดโทรศัพท์"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"กำลังอัปเกรดที่จัดเก็บข้อมูลสำหรับสมุดโทรศัพท์"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"แตะเพื่อทำการอัปเกรดให้สมบูรณ์"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"แตะเพื่ออัปเกรดให้เสร็จสมบูรณ์"</string>
<string name="default_directory" msgid="93961630309570294">"สมุดโทรศัพท์"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"อื่นๆ"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"ข้อความเสียงจาก "</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index ff6df97..713fcb7 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Mga Contact"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Nangangailangan ng higit pang memory ang pag-upgrade sa mga contact."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Ina-upgrade ang storage para sa mga contact"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Pindutin upang kumpletuhin ang pag-upgrade."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"I-tap upang kumpletuhin ang upgrade."</string>
<string name="default_directory" msgid="93961630309570294">"Mga Contact"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Iba pa"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Voicemail mula sa/kay "</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 69acd87..17a6f2f 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kişiler"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kişileri yeni sürüme geçirmek için daha fazla bellek gerekiyor."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Kişiler için depolama alanı yeni sürüme geçiriliyor"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Yeni sürüme geçmeyi tamamlamak için dokunun."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Yeni sürüme geçişi tamamlamak için hafifçe dokunun"</string>
<string name="default_directory" msgid="93961630309570294">"Kişiler"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Diğer"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Sesli mesaj gönderen: "</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 3fd7e3d..187e4c8 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Контакти"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Оновлення контактів потребує більше пам’яті."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Оновлення пам’яті для контактів"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Торкніться, щоб завершити оновлення."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Торкніться, щоб завершити оновлення."</string>
<string name="default_directory" msgid="93961630309570294">"Контакти"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Інші"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Голосова пошта від "</string>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur/strings.xml
similarity index 96%
rename from res/values-ur-rPK/strings.xml
rename to res/values-ur/strings.xml
index 3d081d7..16e2833 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"رابطے"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"رابطوں کے اپ گریڈ کیلئے مزید میموری درکار ہے۔"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"رابطوں کیلئے اسٹوریج اپ گریڈ ہو رہا ہے"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"اپ گریڈ مکمل کرنے کیلئے ٹچ کریں۔"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"اپ گریڈ مکمل کرنے کیلئے تھپتھپائیں۔"</string>
<string name="default_directory" msgid="93961630309570294">"رابطے"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"دیگر"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"صوتی میل منجانب "</string>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz/strings.xml
similarity index 97%
rename from res/values-uz-rUZ/strings.xml
rename to res/values-uz/strings.xml
index 9cdeea0..ae88a57 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Kontaktlar"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kontaktlarni yangilash uchun ko‘proq xotira kerak."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Kontaktlar uchun xotirani yangilash"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Yangilashni tugatish uchun bosing."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Yangilashni tugatish uchun bosing."</string>
<string name="default_directory" msgid="93961630309570294">"Kontaktlar"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Boshqa"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Ovozli xabar egasi: "</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index f1fa674..7aa9943 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Danh bạ"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Nâng cấp danh bạ cần thêm bộ nhớ."</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Đang nâng cấp dung lượng cho danh bạ"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Chạm để hoàn tất nâng cấp."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Nhấn để hoàn tất nâng cấp."</string>
<string name="default_directory" msgid="93961630309570294">"Danh bạ"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Khác"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Thư thoại từ "</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index ad4eda6..9114dd4 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -21,8 +21,8 @@
<string name="provider_label" msgid="6012150850819899907">"通讯录"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"联系人升级需要更多的存储空间。"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"正在升级存储器以容纳更多联系人"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"触摸可完成升级。"</string>
- <string name="default_directory" msgid="93961630309570294">"联系人"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"点按即可完成升级。"</string>
+ <string name="default_directory" msgid="93961630309570294">"通讯录"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"其他"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"语音邮件发件人 "</string>
<string name="debug_dump_title" msgid="4916885724165570279">"复制通讯录数据库"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index b03d7bc..291100d 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"通訊錄"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"需要更多記憶體才能將通訊錄升級。"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"正在升級通訊錄儲存空間"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"輕觸即可完成升級。"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"輕按即可完成升級。"</string>
<string name="default_directory" msgid="93961630309570294">"通訊錄"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"其他"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"留言來自 "</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 1e07ef1..f259d83 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"聯絡人"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"需要更多記憶體才能將聯絡人升級。"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"正在升級聯絡人儲存空間"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"輕觸即可完成升級。"</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"輕觸這裡即可完成升級程序。"</string>
<string name="default_directory" msgid="93961630309570294">"聯絡人"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"其他"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"語音郵件寄件者: "</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 1e4adc1..5fa1316 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -21,7 +21,7 @@
<string name="provider_label" msgid="6012150850819899907">"Othintana nabo"</string>
<string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Ukuthuthukisa othintana naye kudinga enye imemori"</string>
<string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Ukuthuthukiswa kwesilondolozi soxhumana nabo"</string>
- <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Thinta ukuqedela ukuthuthukisa."</string>
+ <string name="upgrade_out_of_memory_notification_text" msgid="2581831842693151968">"Thepha ukuze uqedele ukuthuthukiswa."</string>
<string name="default_directory" msgid="93961630309570294">"Othintana nabo"</string>
<string name="local_invisible_directory" msgid="705244318477396120">"Okunye"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Imeyili yezwi kusuka "</string>
diff --git a/run-all-tests.sh b/run-all-tests.sh
new file mode 100755
index 0000000..84a2ce3
--- /dev/null
+++ b/run-all-tests.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -e
+
+cd $ANDROID_BUILD_TOP
+
+. build/envsetup.sh
+
+mmm -j32 packages/providers/ContactsProvider
+adb install -t -r -g $ANDROID_PRODUCT_OUT/system/priv-app/ContactsProvider/ContactsProvider.apk
+adb install -t -r -g $ANDROID_PRODUCT_OUT/data/app/ContactsProviderTests/ContactsProviderTests.apk
+adb install -t -r -g $ANDROID_PRODUCT_OUT/data/app/ContactsProviderTests2/ContactsProviderTests2.apk
+
+runtest() {
+ log=/tmp/$$.log
+ adb shell am instrument -w "${@}" |& tee $log
+ if grep -q FAILURES $log || ! grep -P -q 'OK \([1-9]' $log ; then
+ return 1
+ else
+ return 0
+ fi
+
+}
+
+runtest com.android.providers.contacts.tests
+runtest com.android.providers.contacts.tests2
\ No newline at end of file
diff --git a/src/com/android/providers/contacts/AbstractContactsProvider.java b/src/com/android/providers/contacts/AbstractContactsProvider.java
index 0e67d10..1bc59e7 100644
--- a/src/com/android/providers/contacts/AbstractContactsProvider.java
+++ b/src/com/android/providers/contacts/AbstractContactsProvider.java
@@ -84,7 +84,7 @@
/**
* The DB helper to use for this content provider.
*/
- private SQLiteOpenHelper mDbHelper;
+ private ContactsDatabaseHelper mDbHelper;
/**
* The database helper to serialize all transactions on. If non-null, any new transaction
@@ -133,12 +133,12 @@
@Override
public boolean onCreate() {
Context context = getContext();
- mDbHelper = getDatabaseHelper(context);
+ mDbHelper = newDatabaseHelper(context);
mTransactionHolder = getTransactionHolder();
return true;
}
- public SQLiteOpenHelper getDatabaseHelper() {
+ public ContactsDatabaseHelper getDatabaseHelper() {
return mDbHelper;
}
@@ -345,8 +345,9 @@
/**
* Gets the database helper for this contacts provider. This is called once, during onCreate().
+ * Do not call in other places.
*/
- protected abstract SQLiteOpenHelper getDatabaseHelper(Context context);
+ protected abstract ContactsDatabaseHelper newDatabaseHelper(Context context);
/**
* Gets the thread-local transaction holder to use for keeping track of the transaction. This
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index 9a5b7c4..c1a50b2 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -751,4 +751,15 @@
adjustForNewPhoneAccountInternal((PhoneAccountHandle) arg);
}
}
+
+ @Override
+ public void shutdown() {
+ if (mBackgroundHandler != null) {
+ mBackgroundHandler.getLooper().quit();
+ try {
+ mBackgroundThread.join();
+ } catch (InterruptedException ignore) {
+ }
+ }
+ }
}
diff --git a/src/com/android/providers/contacts/ContactMetadataProvider.java b/src/com/android/providers/contacts/ContactMetadataProvider.java
index 4396ea6..3cf7df2 100644
--- a/src/com/android/providers/contacts/ContactMetadataProvider.java
+++ b/src/com/android/providers/contacts/ContactMetadataProvider.java
@@ -181,7 +181,7 @@
ensureCaller();
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
@@ -212,7 +212,7 @@
ensureCaller();
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
final int matchedUriId = sURIMatcher.match(uri);
int numDeletes = 0;
@@ -260,7 +260,7 @@
ensureCaller();
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
@@ -296,7 +296,7 @@
Log.v(TAG, "applyBatch: " + operations.size() + " ops");
}
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
ContentProviderResult[] results = super.applyBatch(operations);
db.setTransactionSuccessful();
@@ -315,7 +315,7 @@
Log.v(TAG, "bulkInsert: " + values.length + " inserts");
}
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
final int numValues = super.bulkInsert(uri, values);
db.setTransactionSuccessful();
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 00f08b1..2376ea4 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -16,9 +16,10 @@
package com.android.providers.contacts;
+import com.android.providers.contacts.sqlite.DatabaseAnalyzer;
+import com.android.providers.contacts.sqlite.SqlChecker;
+import com.android.providers.contacts.sqlite.SqlChecker.InvalidSqlException;
import com.android.providers.contacts.util.PropertyUtils;
-import com.google.android.collect.Sets;
-import com.google.common.annotations.VisibleForTesting;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -49,14 +50,19 @@
import android.provider.ContactsContract;
import android.provider.ContactsContract.AggregationExceptions;
import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Identity;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Contacts.Photo;
import android.provider.ContactsContract.Data;
@@ -70,6 +76,7 @@
import android.provider.ContactsContract.PhoneticNameStyle;
import android.provider.ContactsContract.PhotoFiles;
import android.provider.ContactsContract.PinnedPositions;
+import android.provider.ContactsContract.ProviderStatus;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.Settings;
import android.provider.ContactsContract.StatusUpdates;
@@ -81,10 +88,14 @@
import android.text.TextUtils;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Base64;
import android.util.Log;
+import android.util.Slog;
import com.android.common.content.SyncStateContentProviderHelper;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
import com.android.providers.contacts.database.ContactsTableUtil;
import com.android.providers.contacts.database.DeletedContactsTableUtil;
@@ -95,9 +106,9 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
import java.util.Locale;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
/**
* Database helper for contacts. Designed as a singleton to make sure that all
@@ -123,9 +134,11 @@
* 900-999 Lollipop
* 1000-1099 M
* 1100-1199 N
+ * 1200-1299 O
* </pre>
*/
- static final int DATABASE_VERSION = 1111;
+ static final int DATABASE_VERSION = 1201;
+ private static final int MINIMUM_SUPPORTED_VERSION = 700;
public interface Tables {
public static final String CONTACTS = "contacts";
@@ -313,7 +326,12 @@
public static final String ENTITIES = "view_entities";
public static final String RAW_ENTITIES = "view_raw_entities";
public static final String GROUPS = "view_groups";
+
+ /** The data_usage_stat table joined with other tables. */
public static final String DATA_USAGE_STAT = "view_data_usage_stat";
+
+ /** The data_usage_stat table with the low-res columns. */
+ public static final String DATA_USAGE_LR = "view_data_usage";
public static final String STREAM_ITEMS = "view_stream_items";
public static final String METADATA_SYNC = "view_metadata_sync";
public static final String METADATA_SYNC_STATE = "view_metadata_sync_state";
@@ -396,10 +414,12 @@
public static final String CONCRETE_PHOTO_FILE_ID = Tables.CONTACTS + "."
+ Contacts.PHOTO_FILE_ID;
- public static final String CONCRETE_TIMES_CONTACTED = Tables.CONTACTS + "."
- + Contacts.TIMES_CONTACTED;
- public static final String CONCRETE_LAST_TIME_CONTACTED = Tables.CONTACTS + "."
- + Contacts.LAST_TIME_CONTACTED;
+
+ public static final String CONCRETE_RAW_TIMES_CONTACTED = Tables.CONTACTS + "."
+ + Contacts.RAW_TIMES_CONTACTED;
+ public static final String CONCRETE_RAW_LAST_TIME_CONTACTED = Tables.CONTACTS + "."
+ + Contacts.RAW_LAST_TIME_CONTACTED;
+
public static final String CONCRETE_STARRED = Tables.CONTACTS + "." + Contacts.STARRED;
public static final String CONCRETE_PINNED = Tables.CONTACTS + "." + Contacts.PINNED;
public static final String CONCRETE_CUSTOM_RINGTONE = Tables.CONTACTS + "."
@@ -444,10 +464,10 @@
Tables.RAW_CONTACTS + "." + RawContacts.CUSTOM_RINGTONE;
public static final String CONCRETE_SEND_TO_VOICEMAIL =
Tables.RAW_CONTACTS + "." + RawContacts.SEND_TO_VOICEMAIL;
- public static final String CONCRETE_LAST_TIME_CONTACTED =
- Tables.RAW_CONTACTS + "." + RawContacts.LAST_TIME_CONTACTED;
- public static final String CONCRETE_TIMES_CONTACTED =
- Tables.RAW_CONTACTS + "." + RawContacts.TIMES_CONTACTED;
+ public static final String CONCRETE_RAW_LAST_TIME_CONTACTED =
+ Tables.RAW_CONTACTS + "." + RawContacts.RAW_LAST_TIME_CONTACTED;
+ public static final String CONCRETE_RAW_TIMES_CONTACTED =
+ Tables.RAW_CONTACTS + "." + RawContacts.RAW_TIMES_CONTACTED;
public static final String CONCRETE_STARRED =
Tables.RAW_CONTACTS + "." + RawContacts.STARRED;
public static final String CONCRETE_PINNED =
@@ -714,14 +734,24 @@
public static final String CONCRETE_DATA_ID = Tables.DATA_USAGE_STAT + "." + DATA_ID;
/** type: INTEGER (long) */
- public static final String LAST_TIME_USED = "last_time_used";
- public static final String CONCRETE_LAST_TIME_USED =
- Tables.DATA_USAGE_STAT + "." + LAST_TIME_USED;
+ public static final String RAW_LAST_TIME_USED = Data.RAW_LAST_TIME_USED;
+ public static final String LR_LAST_TIME_USED = Data.LR_LAST_TIME_USED;
/** type: INTEGER */
- public static final String TIMES_USED = "times_used";
- public static final String CONCRETE_TIMES_USED =
- Tables.DATA_USAGE_STAT + "." + TIMES_USED;
+ public static final String RAW_TIMES_USED = Data.RAW_TIMES_USED;
+ public static final String LR_TIMES_USED = Data.LR_TIMES_USED;
+
+ public static final String CONCRETE_RAW_LAST_TIME_USED =
+ Tables.DATA_USAGE_STAT + "." + RAW_LAST_TIME_USED;
+
+ public static final String CONCRETE_RAW_TIMES_USED =
+ Tables.DATA_USAGE_STAT + "." + RAW_TIMES_USED;
+
+ public static final String CONCRETE_LR_LAST_TIME_USED =
+ Tables.DATA_USAGE_STAT + "." + LR_LAST_TIME_USED;
+
+ public static final String CONCRETE_LR_TIMES_USED =
+ Tables.DATA_USAGE_STAT + "." + LR_TIMES_USED;
/** type: INTEGER */
public static final String USAGE_TYPE_INT = "usage_type";
@@ -770,26 +800,6 @@
public static final int ADDRESS = 2;
}
- private interface Upgrade303Query {
- public static final String TABLE = Tables.DATA;
-
- public static final String SELECTION =
- DataColumns.MIMETYPE_ID + "=?" +
- " AND " + Data._ID + " NOT IN " +
- "(SELECT " + NameLookupColumns.DATA_ID + " FROM " + Tables.NAME_LOOKUP + ")" +
- " AND " + Data.DATA1 + " NOT NULL";
-
- public static final String COLUMNS[] = {
- Data._ID,
- Data.RAW_CONTACT_ID,
- Data.DATA1,
- };
-
- public static final int ID = 0;
- public static final int RAW_CONTACT_ID = 1;
- public static final int DATA1 = 2;
- }
-
private interface StructuredNameQuery {
public static final String TABLE = Tables.DATA;
@@ -823,37 +833,6 @@
public static final int NAME = 2;
}
- private interface StructName205Query {
- String TABLE = Tables.DATA_JOIN_RAW_CONTACTS;
- String COLUMNS[] = {
- DataColumns.CONCRETE_ID,
- Data.RAW_CONTACT_ID,
- RawContacts.DISPLAY_NAME_SOURCE,
- RawContacts.DISPLAY_NAME_PRIMARY,
- StructuredName.PREFIX,
- StructuredName.GIVEN_NAME,
- StructuredName.MIDDLE_NAME,
- StructuredName.FAMILY_NAME,
- StructuredName.SUFFIX,
- StructuredName.PHONETIC_FAMILY_NAME,
- StructuredName.PHONETIC_MIDDLE_NAME,
- StructuredName.PHONETIC_GIVEN_NAME,
- };
-
- int ID = 0;
- int RAW_CONTACT_ID = 1;
- int DISPLAY_NAME_SOURCE = 2;
- int DISPLAY_NAME = 3;
- int PREFIX = 4;
- int GIVEN_NAME = 5;
- int MIDDLE_NAME = 6;
- int FAMILY_NAME = 7;
- int SUFFIX = 8;
- int PHONETIC_FAMILY_NAME = 9;
- int PHONETIC_MIDDLE_NAME = 10;
- int PHONETIC_GIVEN_NAME = 11;
- }
-
private interface RawContactNameQuery {
public static final String RAW_SQL =
"SELECT "
@@ -896,21 +875,6 @@
public static final int PHONETIC_NAME_STYLE = 12; // data11
}
- private interface Organization205Query {
- String TABLE = Tables.DATA_JOIN_RAW_CONTACTS;
- String COLUMNS[] = {
- DataColumns.CONCRETE_ID,
- Data.RAW_CONTACT_ID,
- Organization.COMPANY,
- Organization.PHONETIC_NAME,
- };
-
- int ID = 0;
- int RAW_CONTACT_ID = 1;
- int COMPANY = 2;
- int PHONETIC_NAME = 3;
- }
-
public final static class NameLookupType {
public static final int NAME_EXACT = 0;
public static final int NAME_VARIANT = 1;
@@ -957,62 +921,101 @@
}
}
+ /** Placeholder for the methods to build the "low-res" SQL expressions. */
+ @VisibleForTesting
+ interface LowRes {
+ /** To be replaced with a real column name. Only used within this interface. */
+ String TEMPLATE_PLACEHOLDER = "XX";
+
+ /**
+ * To be replaced with a constant in the expression.
+ * Only used within this interface.
+ */
+ String CONSTANT_PLACEHOLDER = "YY";
+
+ /** Only used within this interface. */
+ int TIMES_USED_GRANULARITY = 10;
+
+ /** Only used within this interface. */
+ int LAST_TIME_USED_GRANULARITY = 24 * 60 * 60;
+
+ /**
+ * Template to build the "low-res times used/contacted". Only used within this interface.
+ * The outermost cast is needed to tell SQLite that the result is of the integer type.
+ */
+ String TEMPLATE_TIMES_USED =
+ ("cast(ifnull((case when (XX) <= 0 then 0"
+ + " when (XX) < (YY) then 1"
+ + " else (cast((XX) as int) / (YY)) * (YY) end), 0) as int)")
+ .replaceAll(CONSTANT_PLACEHOLDER, String.valueOf(TIMES_USED_GRANULARITY));
+
+ /**
+ * Template to build the "low-res last time used/contacted".
+ * Only used within this interface.
+ * The outermost cast is needed to tell SQLite that the result is of the integer type.
+ */
+ String TEMPLATE_LAST_TIME_USED =
+ ("cast((cast((XX) as int) / (YY)) * (YY) as int)")
+ .replaceAll(CONSTANT_PLACEHOLDER, String.valueOf(LAST_TIME_USED_GRANULARITY));
+
+ /**
+ * Build the SQL expression for the "low-res times used/contacted" expression from the
+ * give column name.
+ */
+ static String getTimesUsedExpression(String column) {
+ return TEMPLATE_TIMES_USED.replaceAll(TEMPLATE_PLACEHOLDER, column);
+ }
+
+ /**
+ * Build the SQL expression for the "low-res last time used/contacted" expression from the
+ * give column name.
+ */
+ static String getLastTimeUsedExpression(String column) {
+ return TEMPLATE_LAST_TIME_USED.replaceAll(TEMPLATE_PLACEHOLDER, column);
+ }
+ }
+
private static final String TAG = "ContactsDatabaseHelper";
private static final String DATABASE_NAME = "contacts2.db";
- private static final String DATABASE_PRESENCE = "presence_db";
private static ContactsDatabaseHelper sSingleton = null;
- /** In-memory cache of previously found MIME-type mappings */
+ /** In-memory map of commonly found MIME-types to their ids in the MIMETYPES table */
@VisibleForTesting
- final ConcurrentHashMap<String, Long> mMimetypeCache = new ConcurrentHashMap<>();
+ final ArrayMap<String, Long> mCommonMimeTypeIdsCache = new ArrayMap<>();
- /** In-memory cache the packages table */
@VisibleForTesting
- final ConcurrentHashMap<String, Long> mPackageCache = new ConcurrentHashMap<>();
+ static final String[] COMMON_MIME_TYPES = {
+ Email.CONTENT_ITEM_TYPE,
+ Im.CONTENT_ITEM_TYPE,
+ Nickname.CONTENT_ITEM_TYPE,
+ Organization.CONTENT_ITEM_TYPE,
+ Phone.CONTENT_ITEM_TYPE,
+ SipAddress.CONTENT_ITEM_TYPE,
+ StructuredName.CONTENT_ITEM_TYPE,
+ StructuredPostal.CONTENT_ITEM_TYPE,
+ Identity.CONTENT_ITEM_TYPE,
+ android.provider.ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE,
+ GroupMembership.CONTENT_ITEM_TYPE,
+ Note.CONTENT_ITEM_TYPE,
+ Event.CONTENT_ITEM_TYPE,
+ Website.CONTENT_ITEM_TYPE,
+ Relation.CONTENT_ITEM_TYPE,
+ "vnd.com.google.cursor.item/contact_misc"
+ };
private final Context mContext;
private final boolean mDatabaseOptimizationEnabled;
+ private final boolean mIsTestInstance;
private final SyncStateContentProviderHelper mSyncState;
private final CountryMonitor mCountryMonitor;
- private long mMimeTypeIdEmail;
- private long mMimeTypeIdIm;
- private long mMimeTypeIdNickname;
- private long mMimeTypeIdOrganization;
- private long mMimeTypeIdPhone;
- private long mMimeTypeIdSip;
- private long mMimeTypeIdStructuredName;
- private long mMimeTypeIdStructuredPostal;
-
- /** Compiled statements for querying and inserting mappings */
- private SQLiteStatement mContactIdQuery;
- private SQLiteStatement mAggregationModeQuery;
- private SQLiteStatement mDataMimetypeQuery;
-
- /** Precompiled SQL statement for setting a data record to the primary. */
- private SQLiteStatement mSetPrimaryStatement;
- /** Precompiled SQL statement for setting a data record to the super primary. */
- private SQLiteStatement mSetSuperPrimaryStatement;
- /** Precompiled SQL statement for clearing super primary of a single record. */
- private SQLiteStatement mClearSuperPrimaryStatement;
- /** Precompiled SQL statement for updating a contact display name */
- private SQLiteStatement mRawContactDisplayNameUpdate;
-
- private SQLiteStatement mNameLookupInsert;
- private SQLiteStatement mNameLookupDelete;
- private SQLiteStatement mStatusUpdateAutoTimestamp;
- private SQLiteStatement mStatusUpdateInsert;
- private SQLiteStatement mStatusUpdateReplace;
- private SQLiteStatement mStatusAttributionUpdate;
- private SQLiteStatement mStatusUpdateDelete;
- private SQLiteStatement mResetNameVerifiedForOtherRawContacts;
- private SQLiteStatement mContactInDefaultDirectoryQuery;
- private SQLiteStatement mMetadataSyncInsert;
- private SQLiteStatement mMetadataSyncUpdate;
-
- private StringBuilder mSb = new StringBuilder();
+ /**
+ * Time when the DB was created. It's persisted in {@link DbProperties#DATABASE_TIME_CREATED},
+ * but loaded into memory so it can be accessed even when the DB is busy.
+ */
+ private long mDatabaseCreationTime;
private MessageDigest mMessageDigest;
{
@@ -1032,7 +1035,8 @@
public static synchronized ContactsDatabaseHelper getInstance(Context context) {
if (sSingleton == null) {
- sSingleton = new ContactsDatabaseHelper(context, DATABASE_NAME, true);
+ sSingleton = new ContactsDatabaseHelper(context, DATABASE_NAME, true,
+ /* isTestInstance=*/ false);
}
return sSingleton;
}
@@ -1041,16 +1045,20 @@
* Returns a new instance for unit tests.
*/
@NeededForTesting
- static ContactsDatabaseHelper getNewInstanceForTest(Context context) {
- return new ContactsDatabaseHelper(context, null, false);
+ public static ContactsDatabaseHelper getNewInstanceForTest(Context context, String filename) {
+ return new ContactsDatabaseHelper(context, filename, false, /* isTestInstance=*/ true);
}
protected ContactsDatabaseHelper(
- Context context, String databaseName, boolean optimizationEnabled) {
- super(context, databaseName, null, DATABASE_VERSION);
+ Context context, String databaseName, boolean optimizationEnabled,
+ boolean isTestInstance) {
+ super(context, databaseName, null, DATABASE_VERSION, MINIMUM_SUPPORTED_VERSION, null);
+ boolean enableWal = android.provider.Settings.Global.getInt(context.getContentResolver(),
+ android.provider.Settings.Global.CONTACTS_DATABASE_WAL_ENABLED, 1) == 1;
+ setWriteAheadLoggingEnabled(enableWal);
mDatabaseOptimizationEnabled = optimizationEnabled;
+ mIsTestInstance = isTestInstance;
Resources resources = context.getResources();
-
mContext = context;
mSyncState = new SyncStateContentProviderHelper();
mCountryMonitor = new CountryMonitor(context);
@@ -1063,61 +1071,70 @@
}
/**
- * Clear all the cached database information and re-initialize it.
+ * Populate ids of known mimetypes into a map for easy access
*
* @param db target database
*/
- private void refreshDatabaseCaches(SQLiteDatabase db) {
- mStatusUpdateDelete = null;
- mStatusUpdateReplace = null;
- mStatusUpdateInsert = null;
- mStatusUpdateAutoTimestamp = null;
- mStatusAttributionUpdate = null;
- mResetNameVerifiedForOtherRawContacts = null;
- mRawContactDisplayNameUpdate = null;
- mSetPrimaryStatement = null;
- mClearSuperPrimaryStatement = null;
- mSetSuperPrimaryStatement = null;
- mNameLookupInsert = null;
- mNameLookupDelete = null;
- mDataMimetypeQuery = null;
- mContactIdQuery = null;
- mAggregationModeQuery = null;
- mContactInDefaultDirectoryQuery = null;
-
- initializeCache(db);
+ private void prepopulateCommonMimeTypes(SQLiteDatabase db) {
+ mCommonMimeTypeIdsCache.clear();
+ for(String commonMimeType: COMMON_MIME_TYPES) {
+ mCommonMimeTypeIdsCache.put(commonMimeType, insertMimeType(db, commonMimeType));
+ }
}
- /**
- * (Re-)initialize the cached database information.
- *
- * @param db target database
- */
- private void initializeCache(SQLiteDatabase db) {
- mMimetypeCache.clear();
- mPackageCache.clear();
-
- // TODO: This could be optimized into one query instead of 7
- // Also: We shouldn't have those fields in the first place. This should just be
- // in the cache
- mMimeTypeIdEmail = lookupMimeTypeId(Email.CONTENT_ITEM_TYPE, db);
- mMimeTypeIdIm = lookupMimeTypeId(Im.CONTENT_ITEM_TYPE, db);
- mMimeTypeIdNickname = lookupMimeTypeId(Nickname.CONTENT_ITEM_TYPE, db);
- mMimeTypeIdOrganization = lookupMimeTypeId(Organization.CONTENT_ITEM_TYPE, db);
- mMimeTypeIdPhone = lookupMimeTypeId(Phone.CONTENT_ITEM_TYPE, db);
- mMimeTypeIdSip = lookupMimeTypeId(SipAddress.CONTENT_ITEM_TYPE, db);
- mMimeTypeIdStructuredName = lookupMimeTypeId(StructuredName.CONTENT_ITEM_TYPE, db);
- mMimeTypeIdStructuredPostal = lookupMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE, db);
+ @Override
+ public void onBeforeDelete(SQLiteDatabase db) {
+ Log.w(TAG, "Database version " + db.getVersion() + " for " + DATABASE_NAME
+ + " is no longer supported. Data will be lost on upgrading to " + DATABASE_VERSION);
}
@Override
public void onOpen(SQLiteDatabase db) {
- refreshDatabaseCaches(db);
-
+ Log.d(TAG, "WAL enabled for " + getDatabaseName() + ": " + db.isWriteAheadLoggingEnabled());
+ prepopulateCommonMimeTypes(db);
mSyncState.onDatabaseOpened(db);
+ // Deleting any state from the presence tables to mimic their behavior from the time they
+ // were in-memory tables
+ db.execSQL("DELETE FROM " + Tables.PRESENCE + ";");
+ db.execSQL("DELETE FROM " + Tables.AGGREGATED_PRESENCE + ";");
- db.execSQL("ATTACH DATABASE ':memory:' AS " + DATABASE_PRESENCE + ";");
- db.execSQL("CREATE TABLE IF NOT EXISTS " + DATABASE_PRESENCE + "." + Tables.PRESENCE + " ("+
+ loadDatabaseCreationTime(db);
+ }
+
+ protected void setDatabaseCreationTime(SQLiteDatabase db) {
+ // Note we don't do this in the profile DB helper.
+ mDatabaseCreationTime = System.currentTimeMillis();
+ PropertyUtils.setProperty(db, DbProperties.DATABASE_TIME_CREATED, String.valueOf(
+ mDatabaseCreationTime));
+ }
+
+ protected void loadDatabaseCreationTime(SQLiteDatabase db) {
+ // Note we don't do this in the profile DB helper.
+
+ mDatabaseCreationTime = 0;
+ final String timestamp = PropertyUtils.getProperty(db,
+ DbProperties.DATABASE_TIME_CREATED, "");
+ if (!TextUtils.isEmpty(timestamp)) {
+ try {
+ mDatabaseCreationTime = Long.parseLong(timestamp);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "Failed to parse timestamp: " + timestamp);
+ }
+ }
+ if (AbstractContactsProvider.VERBOSE_LOGGING) {
+ Log.v(TAG, "Open: creation time=" + mDatabaseCreationTime);
+ }
+ if (mDatabaseCreationTime == 0) {
+ Log.w(TAG, "Unable to load creating time; resetting.");
+ // Hmm, failed to load the timestamp. Just set the current time then.
+ mDatabaseCreationTime = System.currentTimeMillis();
+ PropertyUtils.setProperty(db,
+ DbProperties.DATABASE_TIME_CREATED, Long.toString(mDatabaseCreationTime));
+ }
+ }
+
+ private void createPresenceTables(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.PRESENCE + " ("+
StatusUpdates.DATA_ID + " INTEGER PRIMARY KEY REFERENCES data(_id)," +
StatusUpdates.PROTOCOL + " INTEGER NOT NULL," +
StatusUpdates.CUSTOM_PROTOCOL + " TEXT," +
@@ -1131,21 +1148,21 @@
+ ", " + StatusUpdates.IM_HANDLE + ", " + StatusUpdates.IM_ACCOUNT + ")" +
");");
- db.execSQL("CREATE INDEX IF NOT EXISTS " + DATABASE_PRESENCE + ".presenceIndex" + " ON "
+ db.execSQL("CREATE INDEX IF NOT EXISTS presenceIndex" + " ON "
+ Tables.PRESENCE + " (" + PresenceColumns.RAW_CONTACT_ID + ");");
- db.execSQL("CREATE INDEX IF NOT EXISTS " + DATABASE_PRESENCE + ".presenceIndex2" + " ON "
+ db.execSQL("CREATE INDEX IF NOT EXISTS presenceIndex2" + " ON "
+ Tables.PRESENCE + " (" + PresenceColumns.CONTACT_ID + ");");
db.execSQL("CREATE TABLE IF NOT EXISTS "
- + DATABASE_PRESENCE + "." + Tables.AGGREGATED_PRESENCE + " ("+
+ + Tables.AGGREGATED_PRESENCE + " ("+
AggregatedPresenceColumns.CONTACT_ID
+ " INTEGER PRIMARY KEY REFERENCES contacts(_id)," +
StatusUpdates.PRESENCE + " INTEGER," +
StatusUpdates.CHAT_CAPABILITY + " INTEGER NOT NULL DEFAULT 0" +
");");
- db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_deleted"
- + " BEFORE DELETE ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE
+ db.execSQL("CREATE TRIGGER IF NOT EXISTS " + Tables.PRESENCE + "_deleted"
+ + " BEFORE DELETE ON " + Tables.PRESENCE
+ " BEGIN "
+ " DELETE FROM " + Tables.AGGREGATED_PRESENCE
+ " WHERE " + AggregatedPresenceColumns.CONTACT_ID + " = " +
@@ -1184,14 +1201,14 @@
+ ")"
+ " AND " + PresenceColumns.CONTACT_ID + "=NEW." + PresenceColumns.CONTACT_ID + ";";
- db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_inserted"
- + " AFTER INSERT ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE
+ db.execSQL("CREATE TRIGGER IF NOT EXISTS " + Tables.PRESENCE + "_inserted"
+ + " AFTER INSERT ON " + Tables.PRESENCE
+ " BEGIN "
+ replaceAggregatePresenceSql
+ " END");
- db.execSQL("CREATE TRIGGER " + DATABASE_PRESENCE + "." + Tables.PRESENCE + "_updated"
- + " AFTER UPDATE ON " + DATABASE_PRESENCE + "." + Tables.PRESENCE
+ db.execSQL("CREATE TRIGGER IF NOT EXISTS " + Tables.PRESENCE + "_updated"
+ + " AFTER UPDATE ON " + Tables.PRESENCE
+ " BEGIN "
+ replaceAggregatePresenceSql
+ " END");
@@ -1199,7 +1216,7 @@
@Override
public void onCreate(SQLiteDatabase db) {
- Log.i(TAG, "Bootstrapping database version: " + DATABASE_VERSION);
+ Log.i(TAG, "Bootstrapping database " + DATABASE_NAME + " version: " + DATABASE_VERSION);
mSyncState.createDatabase(db);
@@ -1207,8 +1224,7 @@
// The create time is needed by BOOT_COMPLETE to send broadcasts.
PropertyUtils.createPropertiesTable(db);
- PropertyUtils.setProperty(db, DbProperties.DATABASE_TIME_CREATED, String.valueOf(
- System.currentTimeMillis()));
+ setDatabaseCreationTime(db);
db.execSQL("CREATE TABLE " + Tables.ACCOUNTS + " (" +
AccountsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
@@ -1217,6 +1233,20 @@
AccountsColumns.DATA_SET + " TEXT" +
");");
+ // Note, there are two sets of the usage stat columns: LR_* and RAW_*.
+ // RAW_* contain the real values, which clients can't access. The column names start
+ // with a special prefix, which clients are prohibited from using in queries (including
+ // "where" of deletes/updates.)
+ // The LR_* columns have the original, public names. The views have the LR columns too,
+ // which contain the "low-res" numbers. The tables, though, do *not* have to have these
+ // columns, because we won't use them anyway. However, because old versions of the tables
+ // had those columns, and SQLite doesn't allow removing existing columns, meaning upgraded
+ // tables will have these LR_* columns anyway. So, in order to make a new database look
+ // the same as an upgraded database, we create the LR columns in a new database too.
+ // Otherwise, we would easily end up with writing SQLs that will run fine in a new DB
+ // but not in an upgraded database, and because all unit tests will run with a new database,
+ // we can't easily catch these sort of issues.
+
// One row per group of contacts corresponding to the same person
db.execSQL("CREATE TABLE " + Tables.CONTACTS + " (" +
BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
@@ -1225,8 +1255,13 @@
Contacts.PHOTO_FILE_ID + " INTEGER REFERENCES photo_files(_id)," +
Contacts.CUSTOM_RINGTONE + " TEXT," +
Contacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," +
- Contacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
- Contacts.LAST_TIME_CONTACTED + " INTEGER," +
+
+ Contacts.RAW_TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
+ Contacts.RAW_LAST_TIME_CONTACTED + " INTEGER," +
+
+ Contacts.LR_TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
+ Contacts.LR_LAST_TIME_CONTACTED + " INTEGER," +
+
Contacts.STARRED + " INTEGER NOT NULL DEFAULT 0," +
Contacts.PINNED + " INTEGER NOT NULL DEFAULT " + PinnedPositions.UNPINNED + "," +
Contacts.HAS_PHONE_NUMBER + " INTEGER NOT NULL DEFAULT 0," +
@@ -1258,8 +1293,13 @@
RawContactsColumns.AGGREGATION_NEEDED + " INTEGER NOT NULL DEFAULT 1," +
RawContacts.CUSTOM_RINGTONE + " TEXT," +
RawContacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," +
- RawContacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
- RawContacts.LAST_TIME_CONTACTED + " INTEGER," +
+
+ RawContacts.RAW_TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
+ RawContacts.RAW_LAST_TIME_CONTACTED + " INTEGER," +
+
+ RawContacts.LR_TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
+ RawContacts.LR_LAST_TIME_CONTACTED + " INTEGER," +
+
RawContacts.STARRED + " INTEGER NOT NULL DEFAULT 0," +
RawContacts.PINNED + " INTEGER NOT NULL DEFAULT " + PinnedPositions.UNPINNED +
"," + RawContacts.DISPLAY_NAME_PRIMARY + " TEXT," +
@@ -1547,8 +1587,13 @@
DataUsageStatColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
DataUsageStatColumns.DATA_ID + " INTEGER NOT NULL, " +
DataUsageStatColumns.USAGE_TYPE_INT + " INTEGER NOT NULL DEFAULT 0, " +
- DataUsageStatColumns.TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " +
- DataUsageStatColumns.LAST_TIME_USED + " INTEGER NOT NULL DEFAULT 0, " +
+
+ DataUsageStatColumns.RAW_TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " +
+ DataUsageStatColumns.RAW_LAST_TIME_USED + " INTEGER NOT NULL DEFAULT 0, " +
+
+ DataUsageStatColumns.LR_TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " +
+ DataUsageStatColumns.LR_LAST_TIME_USED + " INTEGER NOT NULL DEFAULT 0, " +
+
"FOREIGN KEY(" + DataUsageStatColumns.DATA_ID + ") REFERENCES "
+ Tables.DATA + "(" + Data._ID + ")" +
");");
@@ -1591,6 +1636,7 @@
createGroupsView(db);
createContactsTriggers(db);
createContactsIndexes(db, false /* we build stats table later */);
+ createPresenceTables(db);
loadNicknameLookupTable(db);
@@ -1607,16 +1653,23 @@
updateSqliteStats(db);
}
+ postOnCreate();
+ }
+
+ protected void postOnCreate() {
+ // Only do this for the main DB, but not for the profile DB.
+
+ notifyProviderStatusChange(mContext);
+
+ // Trigger all sync adapters.
ContentResolver.requestSync(null /* all accounts */,
ContactsContract.AUTHORITY, new Bundle());
- // Only send broadcasts for regular contacts db.
- if (dbForProfile() == 0) {
- final Intent dbCreatedIntent = new Intent(
- ContactsContract.Intents.CONTACTS_DATABASE_CREATED);
- dbCreatedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(dbCreatedIntent, android.Manifest.permission.READ_CONTACTS);
- }
+ // Send the broadcast.
+ final Intent dbCreatedIntent = new Intent(
+ ContactsContract.Intents.CONTACTS_DATABASE_CREATED);
+ dbCreatedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(dbCreatedIntent, android.Manifest.permission.READ_CONTACTS);
}
protected void initializeAutoIncrementSequences(SQLiteDatabase db) {
@@ -1646,7 +1699,7 @@
}
public void createSearchIndexTable(SQLiteDatabase db, boolean rebuildSqliteStats) {
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
db.execSQL("DROP TABLE IF EXISTS " + Tables.SEARCH_INDEX);
db.execSQL("CREATE VIRTUAL TABLE " + Tables.SEARCH_INDEX
@@ -1829,7 +1882,9 @@
db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_ENTITIES + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_STAT + ";");
+ db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_LR + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.STREAM_ITEMS + ";");
+ db.execSQL("DROP VIEW IF EXISTS " + Views.METADATA_SYNC_STATE + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.METADATA_SYNC + ";");
String dataColumns =
@@ -1896,17 +1951,24 @@
String contactOptionColumns =
ContactsColumns.CONCRETE_CUSTOM_RINGTONE
- + " AS " + RawContacts.CUSTOM_RINGTONE + ","
+ + " AS " + Contacts.CUSTOM_RINGTONE + ","
+ ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
- + " AS " + RawContacts.SEND_TO_VOICEMAIL + ","
- + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
- + " AS " + RawContacts.LAST_TIME_CONTACTED + ","
- + ContactsColumns.CONCRETE_TIMES_CONTACTED
- + " AS " + RawContacts.TIMES_CONTACTED + ","
+ + " AS " + Contacts.SEND_TO_VOICEMAIL + ","
+
+ + ContactsColumns.CONCRETE_RAW_LAST_TIME_CONTACTED
+ + " AS " + Contacts.RAW_LAST_TIME_CONTACTED + ","
+ + ContactsColumns.CONCRETE_RAW_TIMES_CONTACTED
+ + " AS " + Contacts.RAW_TIMES_CONTACTED + ","
+
+ + LowRes.getLastTimeUsedExpression(ContactsColumns.CONCRETE_RAW_LAST_TIME_CONTACTED)
+ + " AS " + Contacts.LR_LAST_TIME_CONTACTED + ","
+ + LowRes.getTimesUsedExpression(ContactsColumns.CONCRETE_RAW_TIMES_CONTACTED)
+ + " AS " + Contacts.LR_TIMES_CONTACTED + ","
+
+ ContactsColumns.CONCRETE_STARRED
- + " AS " + RawContacts.STARRED + ","
+ + " AS " + Contacts.STARRED + ","
+ ContactsColumns.CONCRETE_PINNED
- + " AS " + RawContacts.PINNED;
+ + " AS " + Contacts.PINNED;
String contactNameColumns =
"name_raw_contact." + RawContacts.DISPLAY_NAME_SOURCE
@@ -1972,8 +2034,12 @@
String rawContactOptionColumns =
RawContacts.CUSTOM_RINGTONE + ","
+ RawContacts.SEND_TO_VOICEMAIL + ","
- + RawContacts.LAST_TIME_CONTACTED + ","
- + RawContacts.TIMES_CONTACTED + ","
+ + RawContacts.RAW_LAST_TIME_CONTACTED + ","
+ + LowRes.getLastTimeUsedExpression(RawContacts.RAW_LAST_TIME_CONTACTED)
+ + " AS " + RawContacts.LR_LAST_TIME_CONTACTED + ","
+ + RawContacts.RAW_TIMES_CONTACTED + ","
+ + LowRes.getTimesUsedExpression(RawContacts.RAW_TIMES_CONTACTED)
+ + " AS " + RawContacts.LR_TIMES_CONTACTED + ","
+ RawContacts.STARRED + ","
+ RawContacts.PINNED;
@@ -2010,16 +2076,23 @@
+ " AS " + Contacts.CUSTOM_RINGTONE + ", "
+ contactNameColumns + ", "
+ baseContactColumns + ", "
- + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
- + " AS " + Contacts.LAST_TIME_CONTACTED + ", "
+
+ + ContactsColumns.CONCRETE_RAW_LAST_TIME_CONTACTED
+ + " AS " + Contacts.RAW_LAST_TIME_CONTACTED + ", "
+ + LowRes.getLastTimeUsedExpression(ContactsColumns.CONCRETE_RAW_LAST_TIME_CONTACTED)
+ + " AS " + Contacts.LR_LAST_TIME_CONTACTED + ", "
+
+ ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
+ " AS " + Contacts.SEND_TO_VOICEMAIL + ", "
+ ContactsColumns.CONCRETE_STARRED
+ " AS " + Contacts.STARRED + ", "
+ ContactsColumns.CONCRETE_PINNED
+ " AS " + Contacts.PINNED + ", "
- + ContactsColumns.CONCRETE_TIMES_CONTACTED
- + " AS " + Contacts.TIMES_CONTACTED;
+
+ + ContactsColumns.CONCRETE_RAW_TIMES_CONTACTED
+ + " AS " + Contacts.RAW_TIMES_CONTACTED + ", "
+ + LowRes.getTimesUsedExpression(ContactsColumns.CONCRETE_RAW_TIMES_CONTACTED)
+ + " AS " + Contacts.LR_TIMES_CONTACTED;
String contactsSelect = "SELECT "
+ ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + ","
@@ -2109,15 +2182,34 @@
db.execSQL("CREATE VIEW " + Views.ENTITIES + " AS "
+ entitiesSelect);
+ // Data usage view, with the low res columns, with no joins.
+ final String dataUsageViewSelect = "SELECT "
+ + DataUsageStatColumns._ID + ", "
+ + DataUsageStatColumns.DATA_ID + ", "
+ + DataUsageStatColumns.USAGE_TYPE_INT + ", "
+ + DataUsageStatColumns.RAW_TIMES_USED + ", "
+ + DataUsageStatColumns.RAW_LAST_TIME_USED + ","
+ + LowRes.getTimesUsedExpression(DataUsageStatColumns.RAW_TIMES_USED)
+ + " AS " + DataUsageStatColumns.LR_TIMES_USED + ","
+ + LowRes.getLastTimeUsedExpression(DataUsageStatColumns.RAW_LAST_TIME_USED)
+ + " AS " + DataUsageStatColumns.LR_LAST_TIME_USED
+ + " FROM " + Tables.DATA_USAGE_STAT;
+
+ // When the data_usage_stat table is needed with the low-res columns, use this, which is
+ // faster than the DATA_USAGE_STAT view since it doesn't involve joins.
+ db.execSQL("CREATE VIEW " + Views.DATA_USAGE_LR + " AS " + dataUsageViewSelect);
+
String dataUsageStatSelect = "SELECT "
+ DataUsageStatColumns.CONCRETE_ID + " AS " + DataUsageStatColumns._ID + ", "
+ DataUsageStatColumns.DATA_ID + ", "
+ RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
+ MimetypesColumns.CONCRETE_MIMETYPE + " AS " + Data.MIMETYPE + ", "
+ DataUsageStatColumns.USAGE_TYPE_INT + ", "
- + DataUsageStatColumns.TIMES_USED + ", "
- + DataUsageStatColumns.LAST_TIME_USED
- + " FROM " + Tables.DATA_USAGE_STAT
+ + DataUsageStatColumns.RAW_TIMES_USED + ", "
+ + DataUsageStatColumns.RAW_LAST_TIME_USED + ", "
+ + DataUsageStatColumns.LR_TIMES_USED + ", "
+ + DataUsageStatColumns.LR_LAST_TIME_USED
+ + " FROM " + Views.DATA_USAGE_LR + " AS " + Tables.DATA_USAGE_STAT
+ " JOIN " + Tables.DATA + " ON ("
+ DataColumns.CONCRETE_ID + "=" + DataUsageStatColumns.CONCRETE_DATA_ID + ")"
+ " JOIN " + Tables.RAW_CONTACTS + " ON ("
@@ -2279,34 +2371,10 @@
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion < 99) {
- Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion
- + ", data will be lost!");
+ Log.i(TAG,
+ "Upgrading " + DATABASE_NAME + " from version " + oldVersion + " to " + newVersion);
- db.execSQL("DROP TABLE IF EXISTS " + Tables.CONTACTS + ";");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.RAW_CONTACTS + ";");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.PACKAGES + ";");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.MIMETYPES + ";");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.DATA + ";");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.PHONE_LOOKUP + ";");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.NAME_LOOKUP + ";");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.NICKNAME_LOOKUP + ";");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.GROUPS + ";");
- db.execSQL("DROP TABLE IF EXISTS activities;");
- db.execSQL("DROP TABLE IF EXISTS calls;");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.SETTINGS + ";");
- db.execSQL("DROP TABLE IF EXISTS " + Tables.STATUS_UPDATES + ";");
-
- // TODO: we should not be dropping agg_exceptions and contact_options. In case that
- // table's schema changes, we should try to preserve the data, because it was entered
- // by the user and has never been synched to the server.
- db.execSQL("DROP TABLE IF EXISTS " + Tables.AGGREGATION_EXCEPTIONS + ";");
-
- onCreate(db);
- return;
- }
-
- Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion);
+ prepopulateCommonMimeTypes(db);
boolean upgradeViewsAndTriggers = false;
boolean upgradeNameLookup = false;
@@ -2316,428 +2384,6 @@
boolean rebuildSqliteStats = false;
boolean upgradeLocaleSpecificData = false;
- if (oldVersion == 99) {
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 100) {
- db.execSQL("CREATE INDEX IF NOT EXISTS mimetypes_mimetype_index ON "
- + Tables.MIMETYPES + " ("
- + MimetypesColumns.MIMETYPE + ","
- + MimetypesColumns._ID + ");");
- updateIndexStats(db, Tables.MIMETYPES,
- "mimetypes_mimetype_index", "50 1 1");
-
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 101) {
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 102) {
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 103) {
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 104 || oldVersion == 201) {
- LegacyApiSupport.createSettingsTable(db);
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 105) {
- upgradeToVersion202(db);
- upgradeNameLookup = true;
- oldVersion = 202;
- }
-
- if (oldVersion == 202) {
- upgradeToVersion203(db);
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 203) {
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 204) {
- upgradeToVersion205(db);
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 205) {
- upgrateToVersion206(db);
- upgradeViewsAndTriggers = true;
- oldVersion++;
- }
-
- if (oldVersion == 206) {
- // Fix for the bug where name lookup records for organizations would get removed by
- // unrelated updates of the data rows. No longer needed.
- oldVersion = 300;
- }
-
- if (oldVersion == 300) {
- upgradeViewsAndTriggers = true;
- oldVersion = 301;
- }
-
- if (oldVersion == 301) {
- upgradeViewsAndTriggers = true;
- oldVersion = 302;
- }
-
- if (oldVersion == 302) {
- upgradeEmailToVersion303(db);
- upgradeNicknameToVersion303(db);
- oldVersion = 303;
- }
-
- if (oldVersion == 303) {
- upgradeToVersion304(db);
- oldVersion = 304;
- }
-
- if (oldVersion == 304) {
- upgradeNameLookup = true;
- oldVersion = 305;
- }
-
- if (oldVersion == 305) {
- upgradeToVersion306(db);
- oldVersion = 306;
- }
-
- if (oldVersion == 306) {
- upgradeToVersion307(db);
- oldVersion = 307;
- }
-
- if (oldVersion == 307) {
- upgradeToVersion308(db);
- oldVersion = 308;
- }
-
- // Gingerbread upgrades.
- if (oldVersion < 350) {
- upgradeViewsAndTriggers = true;
- oldVersion = 351;
- }
-
- if (oldVersion == 351) {
- upgradeNameLookup = true;
- oldVersion = 352;
- }
-
- if (oldVersion == 352) {
- upgradeToVersion353(db);
- 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) {
- oldVersion = 408; // Obsolete.
- }
-
- 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 (oldVersion == 414) {
- upgradeToVersion415(db);
- upgradeViewsAndTriggers = true;
- oldVersion = 415;
- }
-
- if (oldVersion == 415) {
- upgradeToVersion416(db);
- oldVersion = 416;
- }
-
- if (oldVersion == 416) {
- upgradeLegacyApiSupport = true;
- oldVersion = 417;
- }
-
- // Honeycomb-MR1 upgrades.
- if (oldVersion < 500) {
- upgradeSearchIndex = true;
- }
-
- if (oldVersion < 501) {
- upgradeSearchIndex = true;
- upgradeToVersion501(db);
- oldVersion = 501;
- }
-
- if (oldVersion < 502) {
- upgradeSearchIndex = true;
- upgradeToVersion502(db);
- oldVersion = 502;
- }
-
- if (oldVersion < 503) {
- upgradeSearchIndex = true;
- oldVersion = 503;
- }
-
- if (oldVersion < 504) {
- upgradeToVersion504(db);
- oldVersion = 504;
- }
-
- if (oldVersion < 600) {
- // This change used to add the profile raw contact ID to the Accounts table. That
- // column is no longer needed (as of version 614) since the profile records are stored in
- // a separate copy of the database for security reasons. So this change is now a no-op.
- upgradeViewsAndTriggers = true;
- oldVersion = 600;
- }
-
- if (oldVersion < 601) {
- upgradeToVersion601(db);
- oldVersion = 601;
- }
-
- if (oldVersion < 602) {
- upgradeToVersion602(db);
- oldVersion = 602;
- }
-
- if (oldVersion < 603) {
- upgradeViewsAndTriggers = true;
- oldVersion = 603;
- }
-
- if (oldVersion < 604) {
- upgradeToVersion604(db);
- oldVersion = 604;
- }
-
- if (oldVersion < 605) {
- upgradeViewsAndTriggers = true;
- // This version used to create the stream item and stream item photos tables, but
- // a newer version of those tables is created in version 609 below. So omitting the
- // creation in this upgrade step to avoid a create->drop->create.
- oldVersion = 605;
- }
-
- if (oldVersion < 606) {
- upgradeViewsAndTriggers = true;
- upgradeLegacyApiSupport = true;
- upgradeToVersion606(db);
- oldVersion = 606;
- }
-
- if (oldVersion < 607) {
- upgradeViewsAndTriggers = true;
- // We added "action" and "action_uri" to groups here, but realized this was not a smart
- // move. This upgrade step has been removed (all dogfood phones that executed this step
- // will have those columns, but that shouldn't hurt. Unfortunately, SQLite makes it
- // hard to remove columns).
- oldVersion = 607;
- }
-
- if (oldVersion < 608) {
- upgradeViewsAndTriggers = true;
- upgradeToVersion608(db);
- oldVersion = 608;
- }
-
- if (oldVersion < 609) {
- // This version used to create the stream item and stream item photos tables, but a
- // newer version of those tables is created in version 613 below. So omitting the
- // creation in this upgrade step to avoid a create->drop->create.
- oldVersion = 609;
- }
-
- if (oldVersion < 610) {
- upgradeToVersion610(db);
- oldVersion = 610;
- }
-
- if (oldVersion < 611) {
- upgradeViewsAndTriggers = true;
- upgradeToVersion611(db);
- oldVersion = 611;
- }
-
- if (oldVersion < 612) {
- upgradeViewsAndTriggers = true;
- upgradeToVersion612(db);
- oldVersion = 612;
- }
-
- if (oldVersion < 613) {
- upgradeToVersion613(db);
- oldVersion = 613;
- }
-
- if (oldVersion < 614) {
- // This creates the "view_stream_items" view.
- upgradeViewsAndTriggers = true;
- oldVersion = 614;
- }
-
- if (oldVersion < 615) {
- upgradeToVersion615(db);
- oldVersion = 615;
- }
-
- if (oldVersion < 616) {
- // This updates the "view_stream_items" view.
- upgradeViewsAndTriggers = true;
- oldVersion = 616;
- }
-
- if (oldVersion < 617) {
- // This version upgrade obsoleted the profile_raw_contact_id field of the Accounts
- // table, but we aren't removing the column because it is very little data (and not
- // referenced anymore). We do need to upgrade the views to handle the simplified
- // per-database "is profile" columns.
- upgradeViewsAndTriggers = true;
- oldVersion = 617;
- }
-
- if (oldVersion < 618) {
- upgradeToVersion618(db);
- oldVersion = 618;
- }
-
- if (oldVersion < 619) {
- upgradeViewsAndTriggers = true;
- oldVersion = 619;
- }
-
- if (oldVersion < 620) {
- upgradeViewsAndTriggers = true;
- oldVersion = 620;
- }
-
- if (oldVersion < 621) {
- upgradeSearchIndex = true;
- oldVersion = 621;
- }
-
- if (oldVersion < 622) {
- upgradeToVersion622(db);
- oldVersion = 622;
- }
-
- if (oldVersion < 623) {
- // Change FTS to normalize names using collation key.
- upgradeSearchIndex = true;
- oldVersion = 623;
- }
-
- if (oldVersion < 624) {
- // Upgraded the SQLite index stats.
- upgradeViewsAndTriggers = true;
- oldVersion = 624;
- }
-
- if (oldVersion < 625) {
- // Fix for search for hyphenated names
- upgradeSearchIndex = true;
- oldVersion = 625;
- }
-
- if (oldVersion < 626) {
- upgradeToVersion626(db);
- upgradeViewsAndTriggers = true;
- oldVersion = 626;
- }
-
- if (oldVersion < 700) {
- rescanDirectories = true;
- oldVersion = 700;
- }
-
if (oldVersion < 701) {
upgradeToVersion701(db);
oldVersion = 701;
@@ -2992,6 +2638,17 @@
oldVersion = 1111;
}
+ if (isUpgradeRequired(oldVersion, newVersion, 1200)) {
+ createPresenceTables(db);
+ oldVersion = 1200;
+ }
+
+ if (isUpgradeRequired(oldVersion, newVersion, 1201)) {
+ upgradeToVersion1201(db);
+ upgradeViewsAndTriggers = true;
+ oldVersion = 1201;
+ }
+
// We extracted "calls" and "voicemail_status" at this point, but we can't remove them here
// yet, until CallLogDatabaseHelper moves the data.
@@ -3046,471 +2703,6 @@
return oldVersion < version && newVersion >= version;
}
- private void upgradeToVersion202(SQLiteDatabase db) {
- db.execSQL(
- "ALTER TABLE " + Tables.PHONE_LOOKUP +
- " ADD " + PhoneLookupColumns.MIN_MATCH + " TEXT;");
-
- db.execSQL("CREATE INDEX phone_lookup_min_match_index ON " + Tables.PHONE_LOOKUP + " (" +
- PhoneLookupColumns.MIN_MATCH + "," +
- PhoneLookupColumns.RAW_CONTACT_ID + "," +
- PhoneLookupColumns.DATA_ID +
- ");");
-
- updateIndexStats(db, Tables.PHONE_LOOKUP,
- "phone_lookup_min_match_index", "10000 2 2 1");
-
- SQLiteStatement update = db.compileStatement(
- "UPDATE " + Tables.PHONE_LOOKUP +
- " SET " + PhoneLookupColumns.MIN_MATCH + "=?" +
- " WHERE " + PhoneLookupColumns.DATA_ID + "=?");
-
- // Populate the new column
- Cursor c = db.query(Tables.PHONE_LOOKUP + " JOIN " + Tables.DATA +
- " ON (" + PhoneLookupColumns.DATA_ID + "=" + DataColumns.CONCRETE_ID + ")",
- new String[] {Data._ID, Phone.NUMBER}, null, null, null, null, null);
- try {
- while (c.moveToNext()) {
- long dataId = c.getLong(0);
- String number = c.getString(1);
- if (!TextUtils.isEmpty(number)) {
- update.bindString(1, PhoneNumberUtils.toCallerIDMinMatch(number));
- update.bindLong(2, dataId);
- update.execute();
- }
- }
- } finally {
- c.close();
- }
- }
-
- private void upgradeToVersion203(SQLiteDatabase db) {
- // Garbage-collect first. A bug in Eclair was sometimes leaving
- // raw_contacts in the database that no longer had contacts associated
- // with them. To avoid failures during this database upgrade, drop
- // the orphaned raw_contacts.
- db.execSQL(
- "DELETE FROM raw_contacts" +
- " WHERE contact_id NOT NULL" +
- " AND contact_id NOT IN (SELECT _id FROM contacts)");
-
- db.execSQL(
- "ALTER TABLE " + Tables.CONTACTS +
- " ADD " + Contacts.NAME_RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)");
- db.execSQL(
- "ALTER TABLE " + Tables.RAW_CONTACTS +
- " ADD contact_in_visible_group INTEGER NOT NULL DEFAULT 0");
-
- // For each Contact, find the RawContact that contributed the display name
- db.execSQL(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" +
- " SELECT " + RawContacts._ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
- " AND " + RawContactsColumns.CONCRETE_DISPLAY_NAME + "=" +
- Tables.CONTACTS + "." + Contacts.DISPLAY_NAME +
- " ORDER BY " + RawContacts._ID +
- " LIMIT 1)"
- );
-
- db.execSQL("CREATE INDEX contacts_name_raw_contact_id_index ON " + Tables.CONTACTS + " (" +
- Contacts.NAME_RAW_CONTACT_ID +
- ");");
-
- // If for some unknown reason we missed some names, let's make sure there are
- // no contacts without a name, picking a raw contact "at random".
- db.execSQL(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.NAME_RAW_CONTACT_ID + "=(" +
- " SELECT " + RawContacts._ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID +
- " ORDER BY " + RawContacts._ID +
- " LIMIT 1)" +
- " WHERE " + Contacts.NAME_RAW_CONTACT_ID + " IS NULL"
- );
-
- // Wipe out DISPLAY_NAME on the Contacts table as it is no longer in use.
- db.execSQL(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.DISPLAY_NAME + "=NULL"
- );
-
- // Copy the IN_VISIBLE_GROUP flag down to all raw contacts to allow
- // indexing on (display_name, in_visible_group)
- db.execSQL(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET contact_in_visible_group=(" +
- "SELECT " + Contacts.IN_VISIBLE_GROUP +
- " FROM " + Tables.CONTACTS +
- " WHERE " + Contacts._ID + "=" + RawContacts.CONTACT_ID + ")" +
- " WHERE " + RawContacts.CONTACT_ID + " NOT NULL"
- );
-
- db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
- "contact_in_visible_group" + "," +
- RawContactsColumns.DISPLAY_NAME + " COLLATE LOCALIZED ASC" +
- ");");
-
- db.execSQL("DROP INDEX contacts_visible_index");
- db.execSQL("CREATE INDEX contacts_visible_index ON " + Tables.CONTACTS + " (" +
- Contacts.IN_VISIBLE_GROUP +
- ");");
- }
-
- private void upgradeToVersion205(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
- + " ADD " + RawContacts.DISPLAY_NAME_ALTERNATIVE + " TEXT;");
- db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
- + " ADD " + RawContacts.PHONETIC_NAME + " TEXT;");
- db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
- + " ADD " + RawContacts.PHONETIC_NAME_STYLE + " INTEGER;");
- db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
- + " ADD " + RawContacts.SORT_KEY_PRIMARY
- + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";");
- db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
- + " ADD " + RawContacts.SORT_KEY_ALTERNATIVE
- + " TEXT COLLATE " + ContactsProvider2.PHONEBOOK_COLLATOR_NAME + ";");
-
- NameSplitter splitter = createNameSplitter();
-
- SQLiteStatement rawContactUpdate = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " +
- RawContacts.DISPLAY_NAME_PRIMARY + "=?," +
- RawContacts.DISPLAY_NAME_ALTERNATIVE + "=?," +
- RawContacts.PHONETIC_NAME + "=?," +
- RawContacts.PHONETIC_NAME_STYLE + "=?," +
- RawContacts.SORT_KEY_PRIMARY + "=?," +
- RawContacts.SORT_KEY_ALTERNATIVE + "=?" +
- " WHERE " + RawContacts._ID + "=?");
-
- upgradeStructuredNamesToVersion205(db, rawContactUpdate, splitter);
- upgradeOrganizationsToVersion205(db, rawContactUpdate, splitter);
-
- db.execSQL("DROP INDEX raw_contact_sort_key1_index");
- db.execSQL("CREATE INDEX raw_contact_sort_key1_index ON " + Tables.RAW_CONTACTS + " (" +
- "contact_in_visible_group" + "," +
- RawContacts.SORT_KEY_PRIMARY +
- ");");
-
- db.execSQL("CREATE INDEX raw_contact_sort_key2_index ON " + Tables.RAW_CONTACTS + " (" +
- "contact_in_visible_group" + "," +
- RawContacts.SORT_KEY_ALTERNATIVE +
- ");");
- }
-
- private void upgradeStructuredNamesToVersion205(
- SQLiteDatabase db, SQLiteStatement rawContactUpdate, NameSplitter splitter) {
-
- // Process structured names to detect the style of the full name and phonetic name.
- long mMimeType;
- try {
- mMimeType = DatabaseUtils.longForQuery(db,
- "SELECT " + MimetypesColumns._ID +
- " FROM " + Tables.MIMETYPES +
- " WHERE " + MimetypesColumns.MIMETYPE
- + "='" + StructuredName.CONTENT_ITEM_TYPE + "'", null);
-
- } catch (SQLiteDoneException e) {
- // No structured names in the database.
- return;
- }
-
- SQLiteStatement structuredNameUpdate = db.compileStatement(
- "UPDATE " + Tables.DATA +
- " SET " +
- StructuredName.FULL_NAME_STYLE + "=?," +
- StructuredName.DISPLAY_NAME + "=?," +
- StructuredName.PHONETIC_NAME_STYLE + "=?" +
- " WHERE " + Data._ID + "=?");
-
- NameSplitter.Name name = new NameSplitter.Name();
- Cursor cursor = db.query(StructName205Query.TABLE,
- StructName205Query.COLUMNS,
- DataColumns.MIMETYPE_ID + "=" + mMimeType, null, null, null, null);
- try {
- while (cursor.moveToNext()) {
- long dataId = cursor.getLong(StructName205Query.ID);
- long rawContactId = cursor.getLong(StructName205Query.RAW_CONTACT_ID);
- int displayNameSource = cursor.getInt(StructName205Query.DISPLAY_NAME_SOURCE);
-
- name.clear();
- name.prefix = cursor.getString(StructName205Query.PREFIX);
- name.givenNames = cursor.getString(StructName205Query.GIVEN_NAME);
- name.middleName = cursor.getString(StructName205Query.MIDDLE_NAME);
- name.familyName = cursor.getString(StructName205Query.FAMILY_NAME);
- name.suffix = cursor.getString(StructName205Query.SUFFIX);
- name.phoneticFamilyName = cursor.getString(StructName205Query.PHONETIC_FAMILY_NAME);
- name.phoneticMiddleName = cursor.getString(StructName205Query.PHONETIC_MIDDLE_NAME);
- name.phoneticGivenName = cursor.getString(StructName205Query.PHONETIC_GIVEN_NAME);
-
- upgradeNameToVersion205(dataId, rawContactId, displayNameSource, name,
- structuredNameUpdate, rawContactUpdate, splitter);
- }
- } finally {
- cursor.close();
- }
- }
-
- private void upgradeNameToVersion205(
- long dataId,
- long rawContactId,
- int displayNameSource,
- NameSplitter.Name name,
- SQLiteStatement structuredNameUpdate,
- SQLiteStatement rawContactUpdate,
- NameSplitter splitter) {
-
- splitter.guessNameStyle(name);
- int unadjustedFullNameStyle = name.fullNameStyle;
- name.fullNameStyle = splitter.getAdjustedFullNameStyle(name.fullNameStyle);
- String displayName = splitter.join(name, true, true);
-
- // Don't update database with the adjusted fullNameStyle as it is locale
- // related
- structuredNameUpdate.bindLong(1, unadjustedFullNameStyle);
- DatabaseUtils.bindObjectToProgram(structuredNameUpdate, 2, displayName);
- structuredNameUpdate.bindLong(3, name.phoneticNameStyle);
- structuredNameUpdate.bindLong(4, dataId);
- structuredNameUpdate.execute();
-
- if (displayNameSource == DisplayNameSources.STRUCTURED_NAME) {
- String displayNameAlternative = splitter.join(name, false, false);
- String phoneticName = splitter.joinPhoneticName(name);
- String sortKey = null;
- String sortKeyAlternative = null;
-
- if (phoneticName != null) {
- sortKey = sortKeyAlternative = phoneticName;
- } else if (name.fullNameStyle == FullNameStyle.CHINESE ||
- name.fullNameStyle == FullNameStyle.CJK) {
- sortKey = sortKeyAlternative = displayName;
- }
-
- if (sortKey == null) {
- sortKey = displayName;
- sortKeyAlternative = displayNameAlternative;
- }
-
- updateRawContact205(rawContactUpdate, rawContactId, displayName,
- displayNameAlternative, name.phoneticNameStyle, phoneticName, sortKey,
- sortKeyAlternative);
- }
- }
-
- private void upgradeOrganizationsToVersion205(
- SQLiteDatabase db, SQLiteStatement rawContactUpdate, NameSplitter splitter) {
-
- final long mimeType = lookupMimeTypeId(db, Organization.CONTENT_ITEM_TYPE);
- SQLiteStatement organizationUpdate = db.compileStatement(
- "UPDATE " + Tables.DATA +
- " SET " +
- Organization.PHONETIC_NAME_STYLE + "=?" +
- " WHERE " + Data._ID + "=?");
-
- Cursor cursor = db.query(Organization205Query.TABLE, Organization205Query.COLUMNS,
- DataColumns.MIMETYPE_ID + "=" + mimeType + " AND "
- + RawContacts.DISPLAY_NAME_SOURCE + "=" + DisplayNameSources.ORGANIZATION,
- null, null, null, null);
- try {
- while (cursor.moveToNext()) {
- long dataId = cursor.getLong(Organization205Query.ID);
- long rawContactId = cursor.getLong(Organization205Query.RAW_CONTACT_ID);
- String company = cursor.getString(Organization205Query.COMPANY);
- String phoneticName = cursor.getString(Organization205Query.PHONETIC_NAME);
-
- int phoneticNameStyle = splitter.guessPhoneticNameStyle(phoneticName);
-
- organizationUpdate.bindLong(1, phoneticNameStyle);
- organizationUpdate.bindLong(2, dataId);
- organizationUpdate.execute();
-
- String sortKey = company;
-
- updateRawContact205(rawContactUpdate, rawContactId, company,
- company, phoneticNameStyle, phoneticName, sortKey, sortKey);
- }
- } finally {
- cursor.close();
- }
- }
-
- private void updateRawContact205(SQLiteStatement rawContactUpdate, long rawContactId,
- String displayName, String displayNameAlternative, int phoneticNameStyle,
- String phoneticName, String sortKeyPrimary, String sortKeyAlternative) {
- bindString(rawContactUpdate, 1, displayName);
- bindString(rawContactUpdate, 2, displayNameAlternative);
- bindString(rawContactUpdate, 3, phoneticName);
- rawContactUpdate.bindLong(4, phoneticNameStyle);
- bindString(rawContactUpdate, 5, sortKeyPrimary);
- bindString(rawContactUpdate, 6, sortKeyAlternative);
- rawContactUpdate.bindLong(7, rawContactId);
- rawContactUpdate.execute();
- }
-
- private void upgrateToVersion206(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
- + " ADD name_verified INTEGER NOT NULL DEFAULT 0;");
- }
-
- /**
- * The {@link ContactsProvider2#update} method was deleting name lookup for new
- * emails during the sync. We need to restore the lost name lookup rows.
- */
- private void upgradeEmailToVersion303(SQLiteDatabase db) {
- final long mimeTypeId = lookupMimeTypeId(db, Email.CONTENT_ITEM_TYPE);
- if (mimeTypeId == -1) {
- return;
- }
-
- ContentValues values = new ContentValues();
-
- // Find all data rows with the mime type "email" that are missing name lookup
- Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS,
- Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)},
- null, null, null);
- try {
- while (cursor.moveToNext()) {
- long dataId = cursor.getLong(Upgrade303Query.ID);
- long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID);
- String value = cursor.getString(Upgrade303Query.DATA1);
- value = extractHandleFromEmailAddress(value);
-
- if (value != null) {
- values.put(NameLookupColumns.DATA_ID, dataId);
- values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId);
- values.put(NameLookupColumns.NAME_TYPE, NameLookupType.EMAIL_BASED_NICKNAME);
- values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value));
- db.insert(Tables.NAME_LOOKUP, null, values);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * The {@link ContactsProvider2#update} method was deleting name lookup for new
- * nicknames during the sync. We need to restore the lost name lookup rows.
- */
- private void upgradeNicknameToVersion303(SQLiteDatabase db) {
- final long mimeTypeId = lookupMimeTypeId(db, Nickname.CONTENT_ITEM_TYPE);
- if (mimeTypeId == -1) {
- return;
- }
-
- ContentValues values = new ContentValues();
-
- // Find all data rows with the mime type "nickname" that are missing name lookup
- Cursor cursor = db.query(Upgrade303Query.TABLE, Upgrade303Query.COLUMNS,
- Upgrade303Query.SELECTION, new String[] {String.valueOf(mimeTypeId)},
- null, null, null);
- try {
- while (cursor.moveToNext()) {
- long dataId = cursor.getLong(Upgrade303Query.ID);
- long rawContactId = cursor.getLong(Upgrade303Query.RAW_CONTACT_ID);
- String value = cursor.getString(Upgrade303Query.DATA1);
-
- values.put(NameLookupColumns.DATA_ID, dataId);
- values.put(NameLookupColumns.RAW_CONTACT_ID, rawContactId);
- values.put(NameLookupColumns.NAME_TYPE, NameLookupType.NICKNAME);
- values.put(NameLookupColumns.NORMALIZED_NAME, NameNormalizer.normalize(value));
- db.insert(Tables.NAME_LOOKUP, null, values);
- }
- } finally {
- cursor.close();
- }
- }
-
- private void upgradeToVersion304(SQLiteDatabase db) {
- // Mimetype table requires an index on mime type.
- db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS mime_type ON " + Tables.MIMETYPES + " (" +
- MimetypesColumns.MIMETYPE +
- ");");
- }
-
- private void upgradeToVersion306(SQLiteDatabase db) {
- // Fix invalid lookup that was used for Exchange contacts (it was not escaped)
- // It happened when a new contact was created AND synchronized
- final StringBuilder lookupKeyBuilder = new StringBuilder();
- final SQLiteStatement updateStatement = db.compileStatement(
- "UPDATE contacts " +
- "SET lookup=? " +
- "WHERE _id=?");
- final Cursor contactIdCursor = db.rawQuery(
- "SELECT DISTINCT contact_id " +
- "FROM raw_contacts " +
- "WHERE deleted=0 AND account_type='com.android.exchange'",
- null);
- try {
- while (contactIdCursor.moveToNext()) {
- final long contactId = contactIdCursor.getLong(0);
- lookupKeyBuilder.setLength(0);
- final Cursor c = db.rawQuery(
- "SELECT account_type, account_name, _id, sourceid, display_name " +
- "FROM raw_contacts " +
- "WHERE contact_id=? " +
- "ORDER BY _id",
- new String[] {String.valueOf(contactId)});
- try {
- while (c.moveToNext()) {
- ContactLookupKey.appendToLookupKey(lookupKeyBuilder,
- c.getString(0),
- c.getString(1),
- c.getLong(2),
- c.getString(3),
- c.getString(4));
- }
- } finally {
- c.close();
- }
-
- if (lookupKeyBuilder.length() == 0) {
- updateStatement.bindNull(1);
- } else {
- updateStatement.bindString(1, Uri.encode(lookupKeyBuilder.toString()));
- }
- updateStatement.bindLong(2, contactId);
-
- updateStatement.execute();
- }
- } finally {
- updateStatement.close();
- contactIdCursor.close();
- }
- }
-
- private void upgradeToVersion307(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE properties (" +
- "property_key TEXT PRIMARY_KEY, " +
- "property_value TEXT" +
- ");");
- }
-
- private void upgradeToVersion308(SQLiteDatabase db) {
- 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");
- }
-
- 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) {
- db.execSQL("DELETE FROM contacts " +
- "WHERE NOT EXISTS (SELECT 1 FROM raw_contacts WHERE contact_id=contacts._id)");
- }
-
private void rebuildNameLookup(SQLiteDatabase db, boolean rebuildSqliteStats) {
db.execSQL("DROP INDEX IF EXISTS name_lookup_index");
insertNameLookup(db);
@@ -3551,7 +2743,6 @@
Log.i(TAG, "Upgrading locale data for " + locales
+ " (ICU v" + ICU.getIcuVersion() + ")");
final long start = SystemClock.elapsedRealtime();
- initializeCache(db);
rebuildLocaleData(db, locales, rebuildSqliteStats);
Log.i(TAG, "Locale update completed in " + (SystemClock.elapsedRealtime() - start) + "ms");
}
@@ -3618,7 +2809,7 @@
private void insertNameLookup(SQLiteDatabase db) {
db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP);
- SQLiteStatement nameLookupInsert = db.compileStatement(
+ final SQLiteStatement nameLookupInsert = db.compileStatement(
"INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "("
+ NameLookupColumns.RAW_CONTACT_ID + ","
+ NameLookupColumns.DATA_ID + ","
@@ -3729,442 +2920,6 @@
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;
- }
-
- 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 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);
- }
- }
- } 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.
- * DEFAULT_DIRECTORY should contain every contact which should be shown to users in default.
- * - if a contact doesn't belong to any account (local contact), it should be in
- * default_directory
- * - if a contact belongs to an account that doesn't have a "default" group, it should be in
- * default_directory
- * - if a contact belongs to an account that has a "default" group (like Google directory,
- * which has "My contacts" group as default), it should be in default_directory.
- *
- * This logic assumes that accounts with the "default" group should have at least one
- * group with AUTO_ADD (implying it is the default group) flag in the groups table.
- */
- private void upgradeToVersion411(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + Tables.DEFAULT_DIRECTORY);
- db.execSQL("CREATE TABLE default_directory (_id INTEGER PRIMARY KEY);");
-
- // Process contacts without an account
- db.execSQL("INSERT OR IGNORE INTO default_directory " +
- " SELECT contact_id " +
- " FROM raw_contacts " +
- " WHERE raw_contacts.account_name IS NULL " +
- " AND raw_contacts.account_type IS NULL ");
-
- // Process accounts that don't have a default group (e.g. Exchange).
- db.execSQL("INSERT OR IGNORE INTO default_directory " +
- " SELECT contact_id " +
- " FROM raw_contacts " +
- " WHERE NOT EXISTS" +
- " (SELECT _id " +
- " FROM groups " +
- " WHERE raw_contacts.account_name = groups.account_name" +
- " AND raw_contacts.account_type = groups.account_type" +
- " AND groups.auto_add != 0)");
-
- final 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 default_directory " +
- " SELECT contact_id " +
- " FROM raw_contacts " +
- " JOIN data " +
- " ON (raw_contacts._id=raw_contact_id)" +
- " WHERE mimetype_id=" + mimetype +
- " AND EXISTS" +
- " (SELECT _id" +
- " FROM groups" +
- " WHERE raw_contacts.account_name = groups.account_name" +
- " AND raw_contacts.account_type = groups.account_type" +
- " AND groups.auto_add != 0)");
- }
-
- private void upgradeToVersion413(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS directories;");
- createDirectoriesTable(db);
- }
-
- private void upgradeToVersion415(SQLiteDatabase db) {
- db.execSQL(
- "ALTER TABLE " + Tables.GROUPS +
- " ADD " + Groups.GROUP_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0");
- db.execSQL(
- "UPDATE " + Tables.GROUPS +
- " SET " + Groups.GROUP_IS_READ_ONLY + "=1" +
- " WHERE " + Groups.SYSTEM_ID + " NOT NULL");
- }
-
- private void upgradeToVersion416(SQLiteDatabase db) {
- db.execSQL("CREATE INDEX phone_lookup_data_id_min_match_index ON " + Tables.PHONE_LOOKUP +
- " (" + PhoneLookupColumns.DATA_ID + ", " + PhoneLookupColumns.MIN_MATCH + ");");
- }
-
- private void upgradeToVersion501(SQLiteDatabase db) {
- // Remove organization rows from the name lookup, we now use search index for that
- db.execSQL("DELETE FROM name_lookup WHERE name_type=5");
- }
-
- private void upgradeToVersion502(SQLiteDatabase db) {
- // Remove Chinese and Korean name lookup - this data is now in the search index
- db.execSQL("DELETE FROM name_lookup WHERE name_type IN (6, 7)");
- }
-
- private void upgradeToVersion504(SQLiteDatabase db) {
- initializeCache(db);
-
- // Find all names with prefixes and recreate display name
- Cursor cursor = db.rawQuery(
- "SELECT " + StructuredName.RAW_CONTACT_ID +
- " FROM " + Tables.DATA +
- " WHERE " + DataColumns.MIMETYPE_ID + "=?"
- + " AND " + StructuredName.PREFIX + " NOT NULL",
- new String[] {String.valueOf(mMimeTypeIdStructuredName)});
-
- try {
- while(cursor.moveToNext()) {
- long rawContactId = cursor.getLong(0);
- updateRawContactDisplayName(db, rawContactId);
- }
-
- } finally {
- cursor.close();
- }
- }
-
- private void upgradeToVersion601(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE data_usage_stat(" +
- "stat_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
- "data_id INTEGER NOT NULL, " +
- "usage_type INTEGER NOT NULL DEFAULT 0, " +
- "times_used INTEGER NOT NULL DEFAULT 0, " +
- "last_time_used INTEGER NOT NULL DEFAULT 0, " +
- "FOREIGN KEY(data_id) REFERENCES data(_id));");
- db.execSQL("CREATE UNIQUE INDEX data_usage_stat_index ON " +
- "data_usage_stat (data_id, usage_type)");
- }
-
- private void upgradeToVersion602(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE calls ADD voicemail_uri TEXT;");
- db.execSQL("ALTER TABLE calls ADD _data TEXT;");
- db.execSQL("ALTER TABLE calls ADD has_content INTEGER;");
- db.execSQL("ALTER TABLE calls ADD mime_type TEXT;");
- db.execSQL("ALTER TABLE calls ADD source_data TEXT;");
- db.execSQL("ALTER TABLE calls ADD source_package TEXT;");
- db.execSQL("ALTER TABLE calls ADD state INTEGER;");
- }
-
- private void upgradeToVersion604(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE voicemail_status (" +
- "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "source_package TEXT UNIQUE NOT NULL," +
- "settings_uri TEXT," +
- "voicemail_access_uri TEXT," +
- "configuration_state INTEGER," +
- "data_channel_state INTEGER," +
- "notification_channel_state INTEGER" +
- ");");
- }
-
- private void upgradeToVersion606(SQLiteDatabase db) {
- db.execSQL("DROP VIEW IF EXISTS view_contacts_restricted;");
- db.execSQL("DROP VIEW IF EXISTS view_data_restricted;");
- db.execSQL("DROP VIEW IF EXISTS view_raw_contacts_restricted;");
- db.execSQL("DROP VIEW IF EXISTS view_raw_entities_restricted;");
- db.execSQL("DROP VIEW IF EXISTS view_entities_restricted;");
- db.execSQL("DROP VIEW IF EXISTS view_data_usage_stat_restricted;");
- db.execSQL("DROP INDEX IF EXISTS contacts_restricted_index");
-
- // We should remove the restricted columns here as well, but unfortunately SQLite doesn't
- // provide ALTER TABLE DROP COLUMN. As they have DEFAULT 0, we can keep but ignore them
- }
-
- private void upgradeToVersion608(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE contacts ADD photo_file_id INTEGER REFERENCES photo_files(_id);");
-
- db.execSQL("CREATE TABLE photo_files(" +
- "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
- "height INTEGER NOT NULL, " +
- "width INTEGER NOT NULL, " +
- "filesize INTEGER NOT NULL);");
- }
-
- private void upgradeToVersion610(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE calls ADD is_read INTEGER;");
- }
-
- private void upgradeToVersion611(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE raw_contacts ADD data_set TEXT DEFAULT NULL;");
- db.execSQL("ALTER TABLE groups ADD data_set TEXT DEFAULT NULL;");
- db.execSQL("ALTER TABLE accounts ADD data_set TEXT DEFAULT NULL;");
-
- db.execSQL("CREATE INDEX raw_contacts_source_id_data_set_index ON raw_contacts " +
- "(sourceid, account_type, account_name, data_set);");
-
- db.execSQL("CREATE INDEX groups_source_id_data_set_index ON groups " +
- "(sourceid, account_type, account_name, data_set);");
- }
-
- private void upgradeToVersion612(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE calls ADD geocoded_location TEXT DEFAULT NULL;");
- // Old calls will not have a geocoded location; new calls will get it when inserted.
- }
-
- private void upgradeToVersion613(SQLiteDatabase db) {
- // The stream item and stream item photos APIs were not in-use by anyone in the time
- // between their initial creation (in v609) and this update. So we're just dropping
- // and re-creating them to get appropriate columns. The delta is as follows:
- // - In stream_items, package_id was replaced by res_package.
- // - In stream_item_photos, picture was replaced by photo_file_id.
- // - Instead of resource IDs for icon and label, we use resource name strings now
- // - Added sync columns
- // - Removed action and action_uri
- // - Text and comments are now nullable
-
- db.execSQL("DROP TABLE IF EXISTS stream_items");
- db.execSQL("DROP TABLE IF EXISTS stream_item_photos");
-
- db.execSQL("CREATE TABLE stream_items(" +
- "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
- "raw_contact_id INTEGER NOT NULL, " +
- "res_package TEXT, " +
- "icon TEXT, " +
- "label TEXT, " +
- "text TEXT, " +
- "timestamp INTEGER NOT NULL, " +
- "comments TEXT, " +
- "stream_item_sync1 TEXT, " +
- "stream_item_sync2 TEXT, " +
- "stream_item_sync3 TEXT, " +
- "stream_item_sync4 TEXT, " +
- "FOREIGN KEY(raw_contact_id) REFERENCES raw_contacts(_id));");
-
- db.execSQL("CREATE TABLE stream_item_photos(" +
- "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
- "stream_item_id INTEGER NOT NULL, " +
- "sort_index INTEGER, " +
- "photo_file_id INTEGER NOT NULL, " +
- "stream_item_photo_sync1 TEXT, " +
- "stream_item_photo_sync2 TEXT, " +
- "stream_item_photo_sync3 TEXT, " +
- "stream_item_photo_sync4 TEXT, " +
- "FOREIGN KEY(stream_item_id) REFERENCES stream_items(_id));");
- }
-
- private void upgradeToVersion615(SQLiteDatabase db) {
- // Old calls will not have up to date values for these columns, they will be filled in
- // as needed.
- db.execSQL("ALTER TABLE calls ADD lookup_uri TEXT DEFAULT NULL;");
- db.execSQL("ALTER TABLE calls ADD matched_number TEXT DEFAULT NULL;");
- db.execSQL("ALTER TABLE calls ADD normalized_number TEXT DEFAULT NULL;");
- db.execSQL("ALTER TABLE calls ADD photo_id INTEGER NOT NULL DEFAULT 0;");
- }
-
- private void upgradeToVersion618(SQLiteDatabase db) {
- // The Settings table needs a data_set column which technically should be part of the
- // primary key but can't be because it may be null. Since SQLite doesn't support nuking
- // the primary key, we'll drop the old table, re-create it, and copy the settings back in.
- db.execSQL("CREATE TEMPORARY TABLE settings_backup(" +
- "account_name STRING NOT NULL," +
- "account_type STRING NOT NULL," +
- "ungrouped_visible INTEGER NOT NULL DEFAULT 0," +
- "should_sync INTEGER NOT NULL DEFAULT 1" +
- ");");
- db.execSQL("INSERT INTO settings_backup " +
- "SELECT account_name, account_type, ungrouped_visible, should_sync" +
- " FROM settings");
- db.execSQL("DROP TABLE settings");
- db.execSQL("CREATE TABLE settings (" +
- "account_name STRING NOT NULL," +
- "account_type STRING NOT NULL," +
- "data_set STRING," +
- "ungrouped_visible INTEGER NOT NULL DEFAULT 0," +
- "should_sync INTEGER NOT NULL DEFAULT 1" +
- ");");
- db.execSQL("INSERT INTO settings " +
- "SELECT account_name, account_type, NULL, ungrouped_visible, should_sync " +
- "FROM settings_backup");
- db.execSQL("DROP TABLE settings_backup");
- }
-
- private void upgradeToVersion622(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE calls ADD formatted_number TEXT DEFAULT NULL;");
- }
-
- private void upgradeToVersion626(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS accounts");
-
- db.execSQL("CREATE TABLE accounts (" +
- "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "account_name TEXT, " +
- "account_type TEXT, " +
- "data_set TEXT" +
- ");");
-
- // Add "account_id" column to groups and raw_contacts
- db.execSQL("ALTER TABLE raw_contacts ADD " +
- "account_id INTEGER REFERENCES accounts(_id)");
- db.execSQL("ALTER TABLE groups ADD " +
- "account_id INTEGER REFERENCES accounts(_id)");
-
- // Update indexes.
- db.execSQL("DROP INDEX IF EXISTS raw_contacts_source_id_index");
- db.execSQL("DROP INDEX IF EXISTS raw_contacts_source_id_data_set_index");
- db.execSQL("DROP INDEX IF EXISTS groups_source_id_index");
- db.execSQL("DROP INDEX IF EXISTS groups_source_id_data_set_index");
-
- db.execSQL("CREATE INDEX raw_contacts_source_id_account_id_index ON raw_contacts ("
- + "sourceid, account_id);");
- db.execSQL("CREATE INDEX groups_source_id_account_id_index ON groups ("
- + "sourceid, account_id);");
-
- // Migrate account_name/account_type/data_set to accounts table
-
- final Set<AccountWithDataSet> accountsWithDataSets = Sets.newHashSet();
- upgradeToVersion626_findAccountsWithDataSets(accountsWithDataSets, db, "raw_contacts");
- upgradeToVersion626_findAccountsWithDataSets(accountsWithDataSets, db, "groups");
-
- for (AccountWithDataSet accountWithDataSet : accountsWithDataSets) {
- db.execSQL("INSERT INTO accounts (account_name,account_type,data_set)VALUES(?, ?, ?)",
- new String[] {
- accountWithDataSet.getAccountName(),
- accountWithDataSet.getAccountType(),
- accountWithDataSet.getDataSet()
- });
- }
- upgradeToVersion626_fillAccountId(db, "raw_contacts");
- upgradeToVersion626_fillAccountId(db, "groups");
- }
-
- private static void upgradeToVersion626_findAccountsWithDataSets(
- Set<AccountWithDataSet> result, SQLiteDatabase db, String table) {
- Cursor c = db.rawQuery(
- "SELECT DISTINCT account_name, account_type, data_set FROM " + table, null);
- try {
- while (c.moveToNext()) {
- result.add(AccountWithDataSet.get(c.getString(0), c.getString(1), c.getString(2)));
- }
- } finally {
- c.close();
- }
- }
-
- private static void upgradeToVersion626_fillAccountId(SQLiteDatabase db, String table) {
- StringBuilder sb = new StringBuilder();
-
- // Set account_id and null out account_name, account_type and data_set
-
- sb.append("UPDATE " + table + " SET account_id = (SELECT _id FROM accounts WHERE ");
-
- addJoinExpressionAllowingNull(sb, table + ".account_name", "accounts.account_name");
- sb.append("AND");
- addJoinExpressionAllowingNull(sb, table + ".account_type", "accounts.account_type");
- sb.append("AND");
- addJoinExpressionAllowingNull(sb, table + ".data_set", "accounts.data_set");
-
- sb.append("), account_name = null, account_type = null, data_set = null");
- db.execSQL(sb.toString());
- }
-
private void upgradeToVersion701(SQLiteDatabase db) {
db.execSQL("UPDATE raw_contacts SET last_time_contacted =" +
" max(ifnull(last_time_contacted, 0), " +
@@ -4637,6 +3392,35 @@
FastScrollingIndexCache.getInstance(mContext).invalidate();
}
+ private void upgradeToVersion1201(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE contacts ADD x_times_contacted INTEGER NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE contacts ADD x_last_time_contacted INTEGER");
+
+ db.execSQL("ALTER TABLE raw_contacts ADD x_times_contacted INTEGER NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE raw_contacts ADD x_last_time_contacted INTEGER");
+
+ db.execSQL("ALTER TABLE data_usage_stat ADD x_times_used INTEGER NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE data_usage_stat ADD x_last_time_used INTEGER NOT NULL DEFAULT 0");
+
+ db.execSQL("UPDATE contacts SET "
+ + "x_times_contacted = ifnull(times_contacted,0),"
+ + "x_last_time_contacted = ifnull(last_time_contacted,0),"
+ + "times_contacted = 0,"
+ + "last_time_contacted = 0");
+
+ db.execSQL("UPDATE raw_contacts SET "
+ + "x_times_contacted = ifnull(times_contacted,0),"
+ + "x_last_time_contacted = ifnull(last_time_contacted,0),"
+ + "times_contacted = 0,"
+ + "last_time_contacted = 0");
+
+ db.execSQL("UPDATE data_usage_stat SET "
+ + "x_times_used = ifnull(times_used,0),"
+ + "x_last_time_used = ifnull(last_time_used,0),"
+ + "times_used = 0,"
+ + "last_time_used = 0");
+ }
+
/**
* This method is only used in upgradeToVersion1101 method, and should not be used in other
* places now. Because data15 is not used to generate hash_id for photo, and the new generating
@@ -4711,20 +3495,52 @@
return tokens[0].getAddress().trim();
}
- private static long lookupMimeTypeId(SQLiteDatabase db, String mimeType) {
- try {
- return DatabaseUtils.longForQuery(db,
- "SELECT " + MimetypesColumns._ID +
- " FROM " + Tables.MIMETYPES +
- " WHERE " + MimetypesColumns.MIMETYPE
- + "='" + mimeType + "'", null);
- } catch (SQLiteDoneException e) {
- // No rows of this type in the database.
- return -1;
+ /**
+ * Inserts a new mimetype into the table Tables.MIMETYPES and returns its id. Use
+ * {@link #lookupMimeTypeId(SQLiteDatabase, String)} to lookup id of a mimetype that is
+ * guaranteed to be in the database
+ *
+ * @param db the SQLiteDatabase object returned by {@link #getWritableDatabase()}
+ * @param mimeType The mimetype to insert
+ * @return the id of the newly inserted row
+ */
+ private long insertMimeType(SQLiteDatabase db, String mimeType) {
+ final String insert = "INSERT INTO " + Tables.MIMETYPES + "("
+ + MimetypesColumns.MIMETYPE +
+ ") VALUES (?)";
+ long id = insertWithOneArgAndReturnId(db, insert, mimeType);
+ if (id >= 0) {
+ return id;
}
+ return lookupMimeTypeId(db, mimeType);
}
- private void bindString(SQLiteStatement stmt, int index, String value) {
+ /**
+ * Looks up Tables.MIMETYPES for the mime type and returns its id. Returns -1 if the mime type
+ * is not found. Use {@link #insertMimeType(SQLiteDatabase, String)} when it is doubtful whether
+ * the mimetype already exists in the table or not.
+ *
+ * @param db
+ * @param mimeType
+ * @return the id of the row containing the mime type or -1 if the mime type was not found.
+ */
+ private long lookupMimeTypeId(SQLiteDatabase db, String mimeType) {
+ Long id = mCommonMimeTypeIdsCache.get(mimeType);
+ if (id != null) {
+ return id;
+ }
+ final String query = "SELECT " +
+ MimetypesColumns._ID + " FROM " + Tables.MIMETYPES + " WHERE "
+ + MimetypesColumns.MIMETYPE +
+ "=?";
+ id = queryIdWithOneArg(db, query, mimeType);
+ if (id < 0) {
+ Log.e(TAG, "Mimetype " + mimeType + " not found in the MIMETYPES table");
+ }
+ return id;
+ }
+
+ private static void bindString(SQLiteStatement stmt, int index, String value) {
if (value == null) {
stmt.bindNull(index);
} else {
@@ -4741,18 +3557,6 @@
}
/**
- * Add a string like "(((column1) = (column2)) OR ((column1) IS NULL AND (column2) IS NULL))"
- */
- private static StringBuilder addJoinExpressionAllowingNull(
- StringBuilder sb, String column1, String column2) {
-
- sb.append("(((").append(column1).append(")=(").append(column2);
- sb.append("))OR((");
- sb.append(column1).append(") IS NULL AND (").append(column2).append(") IS NULL))");
- return sb;
- }
-
- /**
* Adds index stats into the SQLite database to force it to always use the lookup indexes.
*
* Note if you drop a table or an index, the corresponding row will be removed from this table.
@@ -4909,6 +3713,10 @@
updateIndexStats(db, "search_index_segdir",
"sqlite_autoindex_search_index_segdir_1", "9 5 1");
+ updateIndexStats(db, Tables.PRESENCE, "presenceIndex", "1 1");
+ updateIndexStats(db, Tables.PRESENCE, "presenceIndex2", "1 1");
+ updateIndexStats(db, Tables.AGGREGATED_PRESENCE, null, "1");
+
// Force SQLite to reload sqlite_stat1.
db.execSQL("ANALYZE sqlite_master;");
} catch (SQLException e) {
@@ -4958,9 +3766,10 @@
db.execSQL("DELETE FROM " + Tables.DELETED_CONTACTS + ";");
db.execSQL("DELETE FROM " + Tables.MIMETYPES + ";");
db.execSQL("DELETE FROM " + Tables.PACKAGES + ";");
+ db.execSQL("DELETE FROM " + Tables.PRESENCE + ";");
+ db.execSQL("DELETE FROM " + Tables.AGGREGATED_PRESENCE + ";");
- initializeCache(db);
-
+ prepopulateCommonMimeTypes(db);
// Note: we are not removing reference data from Tables.NICKNAME_LOOKUP
}
@@ -4990,53 +3799,11 @@
}
}
- /**
- * Internal method used by {@link #getPackageId} and {@link #getMimeTypeId}.
- *
- * Note in the contacts provider we avoid using synchronization because it could risk deadlocks.
- * So here, instead of using locks, we use ConcurrentHashMap + retry.
- *
- * Note we can't use a transaction here becuause this method is called from
- * onCommitTransaction() too, unfortunately.
- */
- private static long getIdCached(SQLiteDatabase db, ConcurrentHashMap<String, Long> cache,
- String querySql, String insertSql, String value) {
- // First, try the in-memory cache.
- if (cache.containsKey(value)) {
- return cache.get(value);
- }
-
- // Then, try the database.
- long id = queryIdWithOneArg(db, querySql, value);
- if (id >= 0) {
- cache.put(value, id);
- return id;
- }
-
- // Not found in the database. Try inserting.
- id = insertWithOneArgAndReturnId(db, insertSql, value);
- if (id >= 0) {
- cache.put(value, id);
- return id;
- }
-
- // Insert failed, which means a race. Let's retry...
-
- // We log here to detect an infinity loop (which shouldn't happen).
- // Conflicts should be pretty rare, so it shouldn't spam logcat.
- Log.i(TAG, "Cache conflict detected: value=" + value);
- try {
- Thread.sleep(1); // Just wait a little bit before retry.
- } catch (InterruptedException ignore) {
- }
- return getIdCached(db, cache, querySql, insertSql, value);
- }
-
@VisibleForTesting
static long queryIdWithOneArg(SQLiteDatabase db, String sql, String sqlArgument) {
final SQLiteStatement query = db.compileStatement(sql);
try {
- DatabaseUtils.bindObjectToProgram(query, 1, sqlArgument);
+ bindString(query, 1, sqlArgument);
try {
return query.simpleQueryForLong();
} catch (SQLiteDoneException notFound) {
@@ -5051,7 +3818,7 @@
static long insertWithOneArgAndReturnId(SQLiteDatabase db, String sql, String sqlArgument) {
final SQLiteStatement insert = db.compileStatement(sql);
try {
- DatabaseUtils.bindObjectToProgram(insert, 1, sqlArgument);
+ bindString(insert, 1, sqlArgument);
try {
return insert.executeInsert();
} catch (SQLiteConstraintException conflict) {
@@ -5076,57 +3843,59 @@
"INSERT INTO " + Tables.PACKAGES + "("
+ PackagesColumns.PACKAGE +
") VALUES (?)";
- return getIdCached(getWritableDatabase(), mPackageCache, query, insert, packageName);
+
+ SQLiteDatabase db = getWritableDatabase();
+ long id = queryIdWithOneArg(db, query, packageName);
+ if (id >= 0) {
+ return id;
+ }
+ id = insertWithOneArgAndReturnId(db, insert, packageName);
+ if (id >= 0) {
+ return id;
+ }
+ // just in case there was a race while doing insert above
+ return queryIdWithOneArg(db, query, packageName);
}
/**
* Convert a mimetype into an integer, using {@link Tables#MIMETYPES} for
* lookups and possible allocation of new IDs as needed.
*/
- public long getMimeTypeId(String mimetype) {
- return lookupMimeTypeId(mimetype, getWritableDatabase());
- }
-
- private long lookupMimeTypeId(String mimetype, SQLiteDatabase db) {
- final String query =
- "SELECT " + MimetypesColumns._ID +
- " FROM " + Tables.MIMETYPES +
- " WHERE " + MimetypesColumns.MIMETYPE + "=?";
-
- final String insert =
- "INSERT INTO " + Tables.MIMETYPES + "("
- + MimetypesColumns.MIMETYPE +
- ") VALUES (?)";
-
- return getIdCached(db, mMimetypeCache, query, insert, mimetype);
+ public long getMimeTypeId(String mimeType) {
+ SQLiteDatabase db = getWritableDatabase();
+ long id = lookupMimeTypeId(db, mimeType);
+ if (id < 0) {
+ return insertMimeType(db, mimeType);
+ }
+ return id;
}
public long getMimeTypeIdForStructuredName() {
- return mMimeTypeIdStructuredName;
+ return lookupMimeTypeId(getWritableDatabase(), StructuredName.CONTENT_ITEM_TYPE);
}
public long getMimeTypeIdForStructuredPostal() {
- return mMimeTypeIdStructuredPostal;
+ return lookupMimeTypeId(getWritableDatabase(), StructuredPostal.CONTENT_ITEM_TYPE);
}
public long getMimeTypeIdForOrganization() {
- return mMimeTypeIdOrganization;
+ return lookupMimeTypeId(getWritableDatabase(), Organization.CONTENT_ITEM_TYPE);
}
public long getMimeTypeIdForIm() {
- return mMimeTypeIdIm;
+ return lookupMimeTypeId(getWritableDatabase(), Im.CONTENT_ITEM_TYPE);
}
public long getMimeTypeIdForEmail() {
- return mMimeTypeIdEmail;
+ return lookupMimeTypeId(getWritableDatabase(), Email.CONTENT_ITEM_TYPE);
}
public long getMimeTypeIdForPhone() {
- return mMimeTypeIdPhone;
+ return lookupMimeTypeId(getWritableDatabase(), Phone.CONTENT_ITEM_TYPE);
}
public long getMimeTypeIdForSip() {
- return mMimeTypeIdSip;
+ return lookupMimeTypeId(getWritableDatabase(), SipAddress.CONTENT_ITEM_TYPE);
}
/**
@@ -5137,19 +3906,19 @@
* {@code STRUCTURED_PHONETIC_NAME}.
*/
private int getDisplayNameSourceForMimeTypeId(int mimeTypeId) {
- if (mimeTypeId == mMimeTypeIdStructuredName) {
+ if (mimeTypeId == mCommonMimeTypeIdsCache.get(StructuredName.CONTENT_ITEM_TYPE)) {
return DisplayNameSources.STRUCTURED_NAME;
}
- if (mimeTypeId == mMimeTypeIdEmail) {
+ if (mimeTypeId == mCommonMimeTypeIdsCache.get(Email.CONTENT_ITEM_TYPE)) {
return DisplayNameSources.EMAIL;
}
- if (mimeTypeId == mMimeTypeIdPhone) {
+ if (mimeTypeId == mCommonMimeTypeIdsCache.get(Phone.CONTENT_ITEM_TYPE)) {
return DisplayNameSources.PHONE;
}
- if (mimeTypeId == mMimeTypeIdOrganization) {
+ if (mimeTypeId == mCommonMimeTypeIdsCache.get(Organization.CONTENT_ITEM_TYPE)) {
return DisplayNameSources.ORGANIZATION;
}
- if (mimeTypeId == mMimeTypeIdNickname) {
+ if (mimeTypeId == mCommonMimeTypeIdsCache.get(Nickname.CONTENT_ITEM_TYPE)) {
return DisplayNameSources.NICKNAME;
}
return DisplayNameSources.UNDEFINED;
@@ -5159,35 +3928,25 @@
* Find the mimetype for the given {@link Data#_ID}.
*/
public String getDataMimeType(long dataId) {
- if (mDataMimetypeQuery == null) {
- mDataMimetypeQuery = getWritableDatabase().compileStatement(
+ final SQLiteStatement dataMimetypeQuery = getWritableDatabase().compileStatement(
"SELECT " + MimetypesColumns.MIMETYPE +
" FROM " + Tables.DATA_JOIN_MIMETYPES +
" WHERE " + Tables.DATA + "." + Data._ID + "=?");
- }
try {
// Try database query to find mimetype
- DatabaseUtils.bindObjectToProgram(mDataMimetypeQuery, 1, dataId);
- String mimetype = mDataMimetypeQuery.simpleQueryForString();
- return mimetype;
+ dataMimetypeQuery.bindLong(1, dataId);
+ return dataMimetypeQuery.simpleQueryForString();
} catch (SQLiteDoneException e) {
// No valid mapping found, so return null
return null;
}
}
- public void invalidateAllCache() {
- Log.w(TAG, "invalidateAllCache: [" + getClass().getSimpleName() + "]");
-
- mMimetypeCache.clear();
- mPackageCache.clear();
- }
-
/**
* Gets all accounts in the accounts table.
*/
public Set<AccountWithDataSet> getAllAccountsWithDataSets() {
- final Set<AccountWithDataSet> result = Sets.newHashSet();
+ final ArraySet<AccountWithDataSet> result = new ArraySet<>();
Cursor c = getReadableDatabase().rawQuery(
"SELECT DISTINCT " + AccountsColumns._ID + "," + AccountsColumns.ACCOUNT_NAME +
"," + AccountsColumns.ACCOUNT_TYPE + "," + AccountsColumns.DATA_SET +
@@ -5351,14 +4110,12 @@
}
public boolean isContactInDefaultDirectory(SQLiteDatabase db, long contactId) {
- if (mContactInDefaultDirectoryQuery == null) {
- mContactInDefaultDirectoryQuery = db.compileStatement(
+ final SQLiteStatement contactInDefaultDirectoryQuery = db.compileStatement(
"SELECT EXISTS (" +
"SELECT 1 FROM " + Tables.DEFAULT_DIRECTORY +
" WHERE " + Contacts._ID + "=?)");
- }
- mContactInDefaultDirectoryQuery.bindLong(1, contactId);
- return mContactInDefaultDirectoryQuery.simpleQueryForLong() != 0;
+ contactInDefaultDirectoryQuery.bindLong(1, contactId);
+ return contactInDefaultDirectoryQuery.simpleQueryForLong() != 0;
}
/**
@@ -5400,30 +4157,26 @@
* Returns contact ID for the given contact or zero if it is NULL.
*/
public long getContactId(long rawContactId) {
- if (mContactIdQuery == null) {
- mContactIdQuery = getWritableDatabase().compileStatement(
+ final SQLiteStatement contactIdQuery = getWritableDatabase().compileStatement(
"SELECT " + RawContacts.CONTACT_ID +
" FROM " + Tables.RAW_CONTACTS +
" WHERE " + RawContacts._ID + "=?");
- }
try {
- DatabaseUtils.bindObjectToProgram(mContactIdQuery, 1, rawContactId);
- return mContactIdQuery.simpleQueryForLong();
+ contactIdQuery.bindLong(1, rawContactId);
+ return contactIdQuery.simpleQueryForLong();
} catch (SQLiteDoneException e) {
return 0; // No valid mapping found.
}
}
public int getAggregationMode(long rawContactId) {
- if (mAggregationModeQuery == null) {
- mAggregationModeQuery = getWritableDatabase().compileStatement(
+ final SQLiteStatement aggregationModeQuery = getWritableDatabase().compileStatement(
"SELECT " + RawContacts.AGGREGATION_MODE +
" FROM " + Tables.RAW_CONTACTS +
" WHERE " + RawContacts._ID + "=?");
- }
try {
- DatabaseUtils.bindObjectToProgram(mAggregationModeQuery, 1, rawContactId);
- return (int)mAggregationModeQuery.simpleQueryForLong();
+ aggregationModeQuery.bindLong(1, rawContactId);
+ return (int) aggregationModeQuery.simpleQueryForLong();
} catch (SQLiteDoneException e) {
return RawContacts.AGGREGATION_MODE_DISABLED; // No valid row found.
}
@@ -5580,7 +4333,7 @@
return;
}
- SQLiteStatement nicknameLookupInsert = db.compileStatement("INSERT INTO "
+ final SQLiteStatement nicknameLookupInsert = db.compileStatement("INSERT INTO "
+ Tables.NICKNAME_LOOKUP + "(" + NicknameLookupColumns.NAME + ","
+ NicknameLookupColumns.CLUSTER + ") VALUES (?,?)");
@@ -5590,9 +4343,8 @@
for (String name : names) {
String normalizedName = NameNormalizer.normalize(name);
try {
- DatabaseUtils.bindObjectToProgram(nicknameLookupInsert, 1, normalizedName);
- DatabaseUtils.bindObjectToProgram(
- nicknameLookupInsert, 2, String.valueOf(clusterId));
+ nicknameLookupInsert.bindString(1, normalizedName);
+ nicknameLookupInsert.bindString(2, String.valueOf(clusterId));
nicknameLookupInsert.executeInsert();
} catch (SQLiteException e) {
// Print the exception and keep going (this is not a fatal error).
@@ -5733,19 +4485,16 @@
}
public void deleteStatusUpdate(long dataId) {
- if (mStatusUpdateDelete == null) {
- mStatusUpdateDelete = getWritableDatabase().compileStatement(
+ final SQLiteStatement statusUpdateDelete = getWritableDatabase().compileStatement(
"DELETE FROM " + Tables.STATUS_UPDATES +
" WHERE " + StatusUpdatesColumns.DATA_ID + "=?");
- }
- mStatusUpdateDelete.bindLong(1, dataId);
- mStatusUpdateDelete.execute();
+ statusUpdateDelete.bindLong(1, dataId);
+ statusUpdateDelete.execute();
}
public void replaceStatusUpdate(Long dataId, long timestamp, String status, String resPackage,
Integer iconResource, Integer labelResource) {
- if (mStatusUpdateReplace == null) {
- mStatusUpdateReplace = getWritableDatabase().compileStatement(
+ final SQLiteStatement statusUpdateReplace = getWritableDatabase().compileStatement(
"INSERT OR REPLACE INTO " + Tables.STATUS_UPDATES + "("
+ StatusUpdatesColumns.DATA_ID + ", "
+ StatusUpdates.STATUS_TIMESTAMP + ","
@@ -5754,20 +4503,18 @@
+ StatusUpdates.STATUS_ICON + ","
+ StatusUpdates.STATUS_LABEL + ")" +
" VALUES (?,?,?,?,?,?)");
- }
- mStatusUpdateReplace.bindLong(1, dataId);
- mStatusUpdateReplace.bindLong(2, timestamp);
- bindString(mStatusUpdateReplace, 3, status);
- bindString(mStatusUpdateReplace, 4, resPackage);
- bindLong(mStatusUpdateReplace, 5, iconResource);
- bindLong(mStatusUpdateReplace, 6, labelResource);
- mStatusUpdateReplace.execute();
+ statusUpdateReplace.bindLong(1, dataId);
+ statusUpdateReplace.bindLong(2, timestamp);
+ bindString(statusUpdateReplace, 3, status);
+ bindString(statusUpdateReplace, 4, resPackage);
+ bindLong(statusUpdateReplace, 5, iconResource);
+ bindLong(statusUpdateReplace, 6, labelResource);
+ statusUpdateReplace.execute();
}
public void insertStatusUpdate(Long dataId, String status, String resPackage,
Integer iconResource, Integer labelResource) {
- if (mStatusUpdateInsert == null) {
- mStatusUpdateInsert = getWritableDatabase().compileStatement(
+ final SQLiteStatement statusUpdateInsert = getWritableDatabase().compileStatement(
"INSERT INTO " + Tables.STATUS_UPDATES + "("
+ StatusUpdatesColumns.DATA_ID + ", "
+ StatusUpdates.STATUS + ","
@@ -5775,45 +4522,41 @@
+ StatusUpdates.STATUS_ICON + ","
+ StatusUpdates.STATUS_LABEL + ")" +
" VALUES (?,?,?,?,?)");
- }
try {
- mStatusUpdateInsert.bindLong(1, dataId);
- bindString(mStatusUpdateInsert, 2, status);
- bindString(mStatusUpdateInsert, 3, resPackage);
- bindLong(mStatusUpdateInsert, 4, iconResource);
- bindLong(mStatusUpdateInsert, 5, labelResource);
- mStatusUpdateInsert.executeInsert();
+ statusUpdateInsert.bindLong(1, dataId);
+ bindString(statusUpdateInsert, 2, status);
+ bindString(statusUpdateInsert, 3, resPackage);
+ bindLong(statusUpdateInsert, 4, iconResource);
+ bindLong(statusUpdateInsert, 5, labelResource);
+ statusUpdateInsert.executeInsert();
} catch (SQLiteConstraintException e) {
// The row already exists - update it
- if (mStatusUpdateAutoTimestamp == null) {
- mStatusUpdateAutoTimestamp = getWritableDatabase().compileStatement(
+ final SQLiteStatement statusUpdateAutoTimestamp = getWritableDatabase()
+ .compileStatement(
"UPDATE " + Tables.STATUS_UPDATES +
" SET " + StatusUpdates.STATUS_TIMESTAMP + "=?,"
+ StatusUpdates.STATUS + "=?" +
" WHERE " + StatusUpdatesColumns.DATA_ID + "=?"
+ " AND " + StatusUpdates.STATUS + "!=?");
- }
long timestamp = System.currentTimeMillis();
- mStatusUpdateAutoTimestamp.bindLong(1, timestamp);
- bindString(mStatusUpdateAutoTimestamp, 2, status);
- mStatusUpdateAutoTimestamp.bindLong(3, dataId);
- bindString(mStatusUpdateAutoTimestamp, 4, status);
- mStatusUpdateAutoTimestamp.execute();
+ statusUpdateAutoTimestamp.bindLong(1, timestamp);
+ bindString(statusUpdateAutoTimestamp, 2, status);
+ statusUpdateAutoTimestamp.bindLong(3, dataId);
+ bindString(statusUpdateAutoTimestamp, 4, status);
+ statusUpdateAutoTimestamp.execute();
- if (mStatusAttributionUpdate == null) {
- mStatusAttributionUpdate = getWritableDatabase().compileStatement(
+ final SQLiteStatement statusAttributionUpdate = getWritableDatabase().compileStatement(
"UPDATE " + Tables.STATUS_UPDATES +
" SET " + StatusUpdates.STATUS_RES_PACKAGE + "=?,"
+ StatusUpdates.STATUS_ICON + "=?,"
+ StatusUpdates.STATUS_LABEL + "=?" +
" WHERE " + StatusUpdatesColumns.DATA_ID + "=?");
- }
- bindString(mStatusAttributionUpdate, 1, resPackage);
- bindLong(mStatusAttributionUpdate, 2, iconResource);
- bindLong(mStatusAttributionUpdate, 3, labelResource);
- mStatusAttributionUpdate.bindLong(4, dataId);
- mStatusAttributionUpdate.execute();
+ bindString(statusAttributionUpdate, 1, resPackage);
+ bindLong(statusAttributionUpdate, 2, iconResource);
+ bindLong(statusAttributionUpdate, 3, labelResource);
+ statusAttributionUpdate.bindLong(4, dataId);
+ statusAttributionUpdate.execute();
}
}
@@ -6013,8 +4756,7 @@
: localeUtils.getBucketIndex(sortKeyAlternative);
String phonebookLabelAlternative = localeUtils.getBucketLabel(phonebookBucketAlternative);
- if (mRawContactDisplayNameUpdate == null) {
- mRawContactDisplayNameUpdate = db.compileStatement(
+ final SQLiteStatement rawContactDisplayNameUpdate = db.compileStatement(
"UPDATE " + Tables.RAW_CONTACTS +
" SET " +
RawContacts.DISPLAY_NAME_SOURCE + "=?," +
@@ -6029,21 +4771,20 @@
RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + "=?," +
RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + "=?" +
" WHERE " + RawContacts._ID + "=?");
- }
- mRawContactDisplayNameUpdate.bindLong(1, bestDisplayNameSource);
- bindString(mRawContactDisplayNameUpdate, 2, displayNamePrimary);
- bindString(mRawContactDisplayNameUpdate, 3, displayNameAlternative);
- bindString(mRawContactDisplayNameUpdate, 4, bestPhoneticName);
- mRawContactDisplayNameUpdate.bindLong(5, bestPhoneticNameStyle);
- bindString(mRawContactDisplayNameUpdate, 6, sortKeyPrimary);
- bindString(mRawContactDisplayNameUpdate, 7, phonebookLabelPrimary);
- mRawContactDisplayNameUpdate.bindLong(8, phonebookBucketPrimary);
- bindString(mRawContactDisplayNameUpdate, 9, sortKeyAlternative);
- bindString(mRawContactDisplayNameUpdate, 10, phonebookLabelAlternative);
- mRawContactDisplayNameUpdate.bindLong(11, phonebookBucketAlternative);
- mRawContactDisplayNameUpdate.bindLong(12, rawContactId);
- mRawContactDisplayNameUpdate.execute();
+ rawContactDisplayNameUpdate.bindLong(1, bestDisplayNameSource);
+ bindString(rawContactDisplayNameUpdate, 2, displayNamePrimary);
+ bindString(rawContactDisplayNameUpdate, 3, displayNameAlternative);
+ bindString(rawContactDisplayNameUpdate, 4, bestPhoneticName);
+ rawContactDisplayNameUpdate.bindLong(5, bestPhoneticNameStyle);
+ bindString(rawContactDisplayNameUpdate, 6, sortKeyPrimary);
+ bindString(rawContactDisplayNameUpdate, 7, phonebookLabelPrimary);
+ rawContactDisplayNameUpdate.bindLong(8, phonebookBucketPrimary);
+ bindString(rawContactDisplayNameUpdate, 9, sortKeyAlternative);
+ bindString(rawContactDisplayNameUpdate, 10, phonebookLabelAlternative);
+ rawContactDisplayNameUpdate.bindLong(11, phonebookBucketAlternative);
+ rawContactDisplayNameUpdate.bindLong(12, rawContactId);
+ rawContactDisplayNameUpdate.execute();
}
/**
@@ -6054,17 +4795,15 @@
* flag of all data items of this raw contacts
*/
public void setIsPrimary(long rawContactId, long dataId, long mimeTypeId) {
- if (mSetPrimaryStatement == null) {
- mSetPrimaryStatement = getWritableDatabase().compileStatement(
+ final SQLiteStatement setPrimaryStatement = getWritableDatabase().compileStatement(
"UPDATE " + Tables.DATA +
" SET " + Data.IS_PRIMARY + "=(_id=?)" +
" WHERE " + DataColumns.MIMETYPE_ID + "=?" +
" AND " + Data.RAW_CONTACT_ID + "=?");
- }
- mSetPrimaryStatement.bindLong(1, dataId);
- mSetPrimaryStatement.bindLong(2, mimeTypeId);
- mSetPrimaryStatement.bindLong(3, rawContactId);
- mSetPrimaryStatement.execute();
+ setPrimaryStatement.bindLong(1, dataId);
+ setPrimaryStatement.bindLong(2, mimeTypeId);
+ setPrimaryStatement.bindLong(3, rawContactId);
+ setPrimaryStatement.execute();
}
/**
@@ -6072,16 +4811,14 @@
* other raw contacts of the same joined aggregate
*/
public void clearSuperPrimary(long rawContactId, long mimeTypeId) {
- if (mClearSuperPrimaryStatement == null) {
- mClearSuperPrimaryStatement = getWritableDatabase().compileStatement(
+ final SQLiteStatement clearSuperPrimaryStatement = getWritableDatabase().compileStatement(
"UPDATE " + Tables.DATA +
" SET " + Data.IS_SUPER_PRIMARY + "=0" +
" WHERE " + DataColumns.MIMETYPE_ID + "=?" +
" AND " + Data.RAW_CONTACT_ID + "=?");
- }
- mClearSuperPrimaryStatement.bindLong(1, mimeTypeId);
- mClearSuperPrimaryStatement.bindLong(2, rawContactId);
- mClearSuperPrimaryStatement.execute();
+ clearSuperPrimaryStatement.bindLong(1, mimeTypeId);
+ clearSuperPrimaryStatement.bindLong(2, rawContactId);
+ clearSuperPrimaryStatement.execute();
}
/**
@@ -6091,8 +4828,7 @@
* @param dataId the id of the data record to be set to primary.
*/
public void setIsSuperPrimary(long rawContactId, long dataId, long mimeTypeId) {
- if (mSetSuperPrimaryStatement == null) {
- mSetSuperPrimaryStatement = getWritableDatabase().compileStatement(
+ final SQLiteStatement setSuperPrimaryStatement = getWritableDatabase().compileStatement(
"UPDATE " + Tables.DATA +
" SET " + Data.IS_SUPER_PRIMARY + "=(" + Data._ID + "=?)" +
" WHERE " + DataColumns.MIMETYPE_ID + "=?" +
@@ -6103,11 +4839,10 @@
"SELECT " + RawContacts.CONTACT_ID +
" FROM " + Tables.RAW_CONTACTS +
" WHERE " + RawContacts._ID + "=?))");
- }
- mSetSuperPrimaryStatement.bindLong(1, dataId);
- mSetSuperPrimaryStatement.bindLong(2, mimeTypeId);
- mSetSuperPrimaryStatement.bindLong(3, rawContactId);
- mSetSuperPrimaryStatement.execute();
+ setSuperPrimaryStatement.bindLong(1, dataId);
+ setSuperPrimaryStatement.bindLong(2, mimeTypeId);
+ setSuperPrimaryStatement.bindLong(3, rawContactId);
+ setSuperPrimaryStatement.execute();
}
/**
@@ -6118,33 +4853,29 @@
return;
}
- if (mNameLookupInsert == null) {
- mNameLookupInsert = getWritableDatabase().compileStatement(
+ final SQLiteStatement nameLookupInsert = getWritableDatabase().compileStatement(
"INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "("
+ NameLookupColumns.RAW_CONTACT_ID + ","
+ NameLookupColumns.DATA_ID + ","
+ NameLookupColumns.NAME_TYPE + ","
+ NameLookupColumns.NORMALIZED_NAME
+ ") VALUES (?,?,?,?)");
- }
- mNameLookupInsert.bindLong(1, rawContactId);
- mNameLookupInsert.bindLong(2, dataId);
- mNameLookupInsert.bindLong(3, lookupType);
- bindString(mNameLookupInsert, 4, name);
- mNameLookupInsert.executeInsert();
+ nameLookupInsert.bindLong(1, rawContactId);
+ nameLookupInsert.bindLong(2, dataId);
+ nameLookupInsert.bindLong(3, lookupType);
+ bindString(nameLookupInsert, 4, name);
+ nameLookupInsert.executeInsert();
}
/**
* Deletes all {@link Tables#NAME_LOOKUP} table rows associated with the specified data element.
*/
public void deleteNameLookup(long dataId) {
- if (mNameLookupDelete == null) {
- mNameLookupDelete = getWritableDatabase().compileStatement(
+ final SQLiteStatement nameLookupDelete = getWritableDatabase().compileStatement(
"DELETE FROM " + Tables.NAME_LOOKUP +
" WHERE " + NameLookupColumns.DATA_ID + "=?");
- }
- mNameLookupDelete.bindLong(1, dataId);
- mNameLookupDelete.execute();
+ nameLookupDelete.bindLong(1, dataId);
+ nameLookupDelete.execute();
}
public String insertNameLookupForEmail(long rawContactId, long dataId, String email) {
@@ -6224,20 +4955,122 @@
}
public long upsertMetadataSync(String backupId, Long accountId, String data, Integer deleted) {
- if (mMetadataSyncInsert == null) {
- mMetadataSyncInsert = getWritableDatabase().compileStatement(
+ final SQLiteStatement metadataSyncInsert = getWritableDatabase().compileStatement(
"INSERT OR REPLACE INTO " + Tables.METADATA_SYNC + "("
+ MetadataSync.RAW_CONTACT_BACKUP_ID + ", "
+ MetadataSyncColumns.ACCOUNT_ID + ", "
+ MetadataSync.DATA + ","
+ MetadataSync.DELETED + ")" +
" VALUES (?,?,?,?)");
- }
- mMetadataSyncInsert.bindString(1, backupId);
- mMetadataSyncInsert.bindLong(2, accountId);
+ metadataSyncInsert.bindString(1, backupId);
+ metadataSyncInsert.bindLong(2, accountId);
data = (data == null) ? "" : data;
- mMetadataSyncInsert.bindString(3, data);
- mMetadataSyncInsert.bindLong(4, deleted);
- return mMetadataSyncInsert.executeInsert();
+ metadataSyncInsert.bindString(3, data);
+ metadataSyncInsert.bindLong(4, deleted);
+ return metadataSyncInsert.executeInsert();
+ }
+
+ public static void notifyProviderStatusChange(Context context) {
+ context.getContentResolver().notifyChange(ProviderStatus.CONTENT_URI,
+ /* observer= */ null, /* syncToNetwork= */ false);
+ }
+
+ public long getDatabaseCreationTime() {
+ return mDatabaseCreationTime;
+ }
+
+ private SqlChecker mCachedSqlChecker;
+
+ private SqlChecker getSqlChecker() {
+ // No need for synchronization on mCachedSqlChecker, because worst-case we'll just
+ // initialize it twice.
+ if (mCachedSqlChecker != null) {
+ return mCachedSqlChecker;
+ }
+ final ArrayList<String> invalidTokens = new ArrayList<>();
+
+ // Disallow referring to tables and views. However, we exempt tables whose names are
+ // also used as column names of any tables. (Right now it's only 'data'.)
+ invalidTokens.addAll(DatabaseAnalyzer.findTableViewsAllowingColumns(getReadableDatabase()));
+
+ // Disallow token "select" to disallow subqueries.
+ invalidTokens.add("select");
+
+ // Allow the use of "default_directory" for now, as it used to be sort of commonly used...
+ invalidTokens.remove(Tables.DEFAULT_DIRECTORY.toLowerCase());
+
+ mCachedSqlChecker = new SqlChecker(invalidTokens);
+
+ return mCachedSqlChecker;
+ }
+
+ /**
+ * Ensure (a piece of) SQL is valid and doesn't contain disallowed tokens.
+ */
+ public void validateSql(String callerPackage, String sqlPiece) {
+ // TODO Replace the Runnable with a lambda -- which would crash right now due to an art bug?
+ runSqlValidation(callerPackage, new Runnable() {
+ @Override
+ public void run() {
+ ContactsDatabaseHelper.this.getSqlChecker().ensureNoInvalidTokens(sqlPiece);
+ }
+ });
+ }
+
+ /**
+ * Ensure all keys in {@code values} are valid. (i.e. they're all single token.)
+ */
+ public void validateContentValues(String callerPackage, ContentValues values) {
+ // TODO Replace the Runnable with a lambda -- which would crash right now due to an art bug?
+ runSqlValidation(callerPackage, new Runnable() {
+ @Override
+ public void run() {
+ for (String key : values.keySet()) {
+ ContactsDatabaseHelper.this.getSqlChecker().ensureSingleTokenOnly(key);
+ }
+ }
+ });
+ }
+
+ /**
+ * Ensure all column names in {@code projection} are valid. (i.e. they're all single token.)
+ */
+ public void validateProjection(String callerPackage, String[] projection) {
+ // TODO Replace the Runnable with a lambda -- which would crash right now due to an art bug?
+ if (projection != null) {
+ runSqlValidation(callerPackage, new Runnable() {
+ @Override
+ public void run() {
+ for (String column : projection) {
+ ContactsDatabaseHelper.this.getSqlChecker().ensureSingleTokenOnly(column);
+ }
+ }
+ });
+ }
+ }
+
+ private void runSqlValidation(String callerPackage, Runnable r) {
+ try {
+ r.run();
+ } catch (InvalidSqlException e) {
+ reportInvalidSql(callerPackage, e);
+ }
+ }
+
+ private void reportInvalidSql(String callerPackage, InvalidSqlException e) {
+ logWtf(String.format("%s caller=%s", e.getMessage(), callerPackage));
+ throw e; // STOPSHIP Don't throw for pre-O apps.
+ }
+
+ /**
+ * Calls WTF without crashing, so we can collect errors in the wild. During unit tests, it'll
+ * log only.
+ */
+ public void logWtf(String message) {
+ if (mIsTestInstance) {
+ Slog.w(TAG, "[Test mode, warning only] " + message);
+ } else {
+ Slog.wtfStack(TAG, message);
+ }
}
}
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index eb5dcfd..f3f85cf 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -66,6 +66,7 @@
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
@@ -220,13 +221,13 @@
private static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
/* package */ static final String UPDATE_TIMES_CONTACTED_CONTACTS_TABLE =
- "UPDATE " + Tables.CONTACTS + " SET " + Contacts.TIMES_CONTACTED + "=" +
- " ifnull(" + Contacts.TIMES_CONTACTED + ",0)+1" +
+ "UPDATE " + Tables.CONTACTS + " SET " + Contacts.RAW_TIMES_CONTACTED + "=" +
+ " ifnull(" + Contacts.RAW_TIMES_CONTACTED + ",0)+1" +
" WHERE " + Contacts._ID + "=?";
/* package */ static final String UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE =
- "UPDATE " + Tables.RAW_CONTACTS + " SET " + RawContacts.TIMES_CONTACTED + "=" +
- " ifnull(" + RawContacts.TIMES_CONTACTED + ",0)+1 " +
+ "UPDATE " + Tables.RAW_CONTACTS + " SET " + RawContacts.RAW_TIMES_CONTACTED + "=" +
+ " ifnull(" + RawContacts.RAW_TIMES_CONTACTED + ",0)+1 " +
" WHERE " + RawContacts.CONTACT_ID + "=?";
/* package */ static final String PHONEBOOK_COLLATOR_NAME = "PHONEBOOK";
@@ -313,7 +314,7 @@
public static final ProfileAwareUriMatcher sUriMatcher =
new ProfileAwareUriMatcher(UriMatcher.NO_MATCH);
- private static final String FREQUENT_ORDER_BY = DataUsageStatColumns.TIMES_USED + " DESC,"
+ private static final String FREQUENT_ORDER_BY = DataUsageStatColumns.RAW_TIMES_USED + " DESC,"
+ Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
public static final int CONTACTS = 1000;
@@ -610,20 +611,23 @@
// Contacts contacted within the last 30 days (in seconds)
private static final long LAST_TIME_USED_30_DAYS_SEC = 30L * 24 * 60 * 60;
- private static final String TIME_SINCE_LAST_USED_SEC =
- "(strftime('%s', 'now') - " + DataUsageStatColumns.LAST_TIME_USED + "/1000)";
+ private static final String RAW_TIME_SINCE_LAST_USED_SEC =
+ "(strftime('%s', 'now') - " + DataUsageStatColumns.RAW_LAST_TIME_USED + "/1000)";
+
+ private static final String LR_TIME_SINCE_LAST_USED_SEC =
+ "(strftime('%s', 'now') - " + DataUsageStatColumns.LR_LAST_TIME_USED + "/1000)";
private static final String SORT_BY_DATA_USAGE =
- "(CASE WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_3_DAYS_SEC +
+ "(CASE WHEN " + RAW_TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_3_DAYS_SEC +
" THEN 0 " +
- " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_7_DAYS_SEC +
+ " WHEN " + RAW_TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_7_DAYS_SEC +
" THEN 1 " +
- " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_14_DAYS_SEC +
+ " WHEN " + RAW_TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_14_DAYS_SEC +
" THEN 2 " +
- " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_30_DAYS_SEC +
+ " WHEN " + RAW_TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_30_DAYS_SEC +
" THEN 3 " +
" ELSE 4 END), " +
- DataUsageStatColumns.TIMES_USED + " DESC";
+ DataUsageStatColumns.RAW_TIMES_USED + " DESC";
/*
* Sorting order for email address suggestions: first starred, then the rest.
@@ -676,7 +680,7 @@
.add(Contacts.DISPLAY_NAME_SOURCE)
.add(Contacts.IN_DEFAULT_DIRECTORY)
.add(Contacts.IN_VISIBLE_GROUP)
- .add(Contacts.LAST_TIME_CONTACTED)
+ .add(Contacts.LR_LAST_TIME_CONTACTED)
.add(Contacts.LOOKUP_KEY)
.add(Contacts.PHONETIC_NAME)
.add(Contacts.PHONETIC_NAME_STYLE)
@@ -693,7 +697,7 @@
.add(ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE)
.add(Contacts.STARRED)
.add(Contacts.PINNED)
- .add(Contacts.TIMES_CONTACTED)
+ .add(Contacts.LR_TIMES_CONTACTED)
.add(Contacts.HAS_PHONE_NUMBER)
.add(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)
.build();
@@ -794,8 +798,8 @@
.build();
private static final ProjectionMap sDataUsageColumns = ProjectionMap.builder()
- .add(Data.TIMES_USED, Tables.DATA_USAGE_STAT + "." + Data.TIMES_USED)
- .add(Data.LAST_TIME_USED, Tables.DATA_USAGE_STAT + "." + Data.LAST_TIME_USED)
+ .add(Data.LR_TIMES_USED, Tables.DATA_USAGE_STAT + "." + Data.LR_TIMES_USED)
+ .add(Data.LR_LAST_TIME_USED, Tables.DATA_USAGE_STAT + "." + Data.LR_LAST_TIME_USED)
.build();
/** Contains just BaseColumns._COUNT */
@@ -822,16 +826,18 @@
/** Used for pushing starred contacts to the top of a times contacted list **/
private static final ProjectionMap sStrequentStarredProjectionMap = ProjectionMap.builder()
.addAll(sContactsProjectionMap)
- .add(DataUsageStatColumns.TIMES_USED, String.valueOf(Long.MAX_VALUE))
- .add(DataUsageStatColumns.LAST_TIME_USED, String.valueOf(Long.MAX_VALUE))
+ .add(DataUsageStatColumns.LR_TIMES_USED, String.valueOf(Long.MAX_VALUE))
+ .add(DataUsageStatColumns.LR_LAST_TIME_USED, String.valueOf(Long.MAX_VALUE))
.build();
private static final ProjectionMap sStrequentFrequentProjectionMap = ProjectionMap.builder()
.addAll(sContactsProjectionMap)
- .add(DataUsageStatColumns.TIMES_USED,
- "SUM(" + DataUsageStatColumns.CONCRETE_TIMES_USED + ")")
- .add(DataUsageStatColumns.LAST_TIME_USED,
- "MAX(" + DataUsageStatColumns.CONCRETE_LAST_TIME_USED + ")")
+ // Note this should ideally be "lowres(SUM)" rather than "SUM(lowres)", but we do it
+ // this way for performance reasons.
+ .add(DataUsageStatColumns.LR_TIMES_USED,
+ "SUM(" + DataUsageStatColumns.CONCRETE_LR_TIMES_USED + ")")
+ .add(DataUsageStatColumns.LR_LAST_TIME_USED,
+ "MAX(" + DataUsageStatColumns.CONCRETE_LR_LAST_TIME_USED + ")")
.build();
/**
@@ -843,8 +849,8 @@
private static final ProjectionMap sStrequentPhoneOnlyProjectionMap
= ProjectionMap.builder()
.addAll(sContactsProjectionMap)
- .add(DataUsageStatColumns.TIMES_USED, DataUsageStatColumns.CONCRETE_TIMES_USED)
- .add(DataUsageStatColumns.LAST_TIME_USED, DataUsageStatColumns.CONCRETE_LAST_TIME_USED)
+ .add(DataUsageStatColumns.LR_TIMES_USED)
+ .add(DataUsageStatColumns.LR_LAST_TIME_USED)
.add(Phone.NUMBER)
.add(Phone.TYPE)
.add(Phone.LABEL)
@@ -876,8 +882,8 @@
.add(RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY)
.add(RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE)
.add(RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE)
- .add(RawContacts.TIMES_CONTACTED)
- .add(RawContacts.LAST_TIME_CONTACTED)
+ .add(RawContacts.LR_TIMES_CONTACTED)
+ .add(RawContacts.LR_LAST_TIME_CONTACTED)
.add(RawContacts.CUSTOM_RINGTONE)
.add(RawContacts.SEND_TO_VOICEMAIL)
.add(RawContacts.STARRED)
@@ -976,9 +982,16 @@
.add(PhoneLookup.CONTACT_ID, "contacts_view." + Contacts._ID)
.add(PhoneLookup.DATA_ID, PhoneLookup.DATA_ID)
.add(PhoneLookup.LOOKUP_KEY, "contacts_view." + Contacts.LOOKUP_KEY)
+ .add(PhoneLookup.DISPLAY_NAME_SOURCE, "contacts_view." + Contacts.DISPLAY_NAME_SOURCE)
.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.DISPLAY_NAME_ALTERNATIVE,
+ "contacts_view." + Contacts.DISPLAY_NAME_ALTERNATIVE)
+ .add(PhoneLookup.PHONETIC_NAME, "contacts_view." + Contacts.PHONETIC_NAME)
+ .add(PhoneLookup.PHONETIC_NAME_STYLE, "contacts_view." + Contacts.PHONETIC_NAME_STYLE)
+ .add(PhoneLookup.SORT_KEY_PRIMARY, "contacts_view." + Contacts.SORT_KEY_PRIMARY)
+ .add(PhoneLookup.SORT_KEY_ALTERNATIVE, "contacts_view." + Contacts.SORT_KEY_ALTERNATIVE)
+ .add(PhoneLookup.LR_LAST_TIME_CONTACTED, "contacts_view." + Contacts.LR_LAST_TIME_CONTACTED)
+ .add(PhoneLookup.LR_TIMES_CONTACTED, "contacts_view." + Contacts.LR_TIMES_CONTACTED)
.add(PhoneLookup.STARRED, "contacts_view." + Contacts.STARRED)
.add(PhoneLookup.IN_DEFAULT_DIRECTORY, "contacts_view." + Contacts.IN_DEFAULT_DIRECTORY)
.add(PhoneLookup.IN_VISIBLE_GROUP, "contacts_view." + Contacts.IN_VISIBLE_GROUP)
@@ -1575,7 +1588,7 @@
mMetadataSyncEnabled = android.provider.Settings.Global.getInt(
getContext().getContentResolver(), Global.CONTACT_METADATA_SYNC_ENABLED, 0) == 1;
- mContactsHelper = getDatabaseHelper(getContext());
+ mContactsHelper = getDatabaseHelper();
mDbHelper.set(mContactsHelper);
// Set up the DB helper for keeping transactions serialized.
@@ -1604,7 +1617,7 @@
ProviderInfo profileInfo = new ProviderInfo();
profileInfo.authority = ContactsContract.AUTHORITY;
mProfileProvider.attachInfo(getContext(), profileInfo);
- mProfileHelper = mProfileProvider.getDatabaseHelper(getContext());
+ mProfileHelper = mProfileProvider.getDatabaseHelper();
mEnterprisePolicyGuard = new EnterprisePolicyGuard(getContext());
// Initialize the pre-authorized URI duration.
@@ -2053,7 +2066,7 @@
}
@Override
- public ContactsDatabaseHelper getDatabaseHelper(final Context context) {
+ public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
return ContactsDatabaseHelper.getInstance(context);
}
@@ -2233,6 +2246,8 @@
public Uri insert(Uri uri, ContentValues values) {
waitForAccess(mWriteAccessLatch);
+ mContactsHelper.validateContentValues(getCallingPackage(), values);
+
if (mapsToProfileDbWithInsertedValues(uri, values)) {
switchToProfileMode();
return mProfileProvider.insert(uri, values);
@@ -2245,6 +2260,9 @@
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
waitForAccess(mWriteAccessLatch);
+ mContactsHelper.validateContentValues(getCallingPackage(), values);
+ mContactsHelper.validateSql(getCallingPackage(), selection);
+
if (mapsToProfileDb(uri)) {
switchToProfileMode();
return mProfileProvider.update(uri, values, selection, selectionArgs);
@@ -2257,6 +2275,8 @@
public int delete(Uri uri, String selection, String[] selectionArgs) {
waitForAccess(mWriteAccessLatch);
+ mContactsHelper.validateSql(getCallingPackage(), selection);
+
if (mapsToProfileDb(uri)) {
switchToProfileMode();
return mProfileProvider.delete(uri, selection, selectionArgs);
@@ -2327,7 +2347,7 @@
if (uri.getQueryParameter(PREAUTHORIZED_URI_TOKEN) != null) {
final long now = Clock.getInstance().currentTimeMillis();
final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
// First delete any pre-authorization URIs that are no longer valid. Unfortunately,
// this operation will grab a write lock for readonly queries. Since this only
@@ -2453,8 +2473,6 @@
} else {
switchToContactMode();
}
-
- mDbHelper.get().invalidateAllCache();
}
private void updateSearchIndexInTransaction() {
@@ -2608,7 +2626,7 @@
protected void notifyChange(boolean syncToNetwork, boolean syncToMetadataNetwork) {
getContext().getContentResolver().notifyChange(ContactsContract.AUTHORITY_URI, null,
- syncToNetwork);
+ syncToNetwork || syncToMetadataNetwork);
getContext().getContentResolver().notifyChange(MetadataSync.METADATA_AUTHORITY_URI,
null, syncToMetadataNetwork);
@@ -2617,7 +2635,7 @@
protected void setProviderStatus(int status) {
if (mProviderStatus != status) {
mProviderStatus = status;
- getContext().getContentResolver().notifyChange(ProviderStatus.CONTENT_URI, null, false);
+ ContactsDatabaseHelper.notifyProviderStatusChange(getContext());
}
}
@@ -2866,6 +2884,8 @@
private long insertRawContact(
Uri uri, ContentValues inputValues, boolean callerIsSyncAdapter) {
+ inputValues = fixUpUsageColumnsForEdit(inputValues);
+
// Create a shallow copy and initialize the contact ID to null.
final ContentValues values = new ContentValues(inputValues);
values.putNull(RawContacts.CONTACT_ID);
@@ -2891,8 +2911,6 @@
if (needToUpdateMetadata) {
mTransactionContext.get().markRawContactMetadataDirty(rawContactId,
/* isMetadataSyncAdapter =*/false);
- mTransactionContext.get().markRawContactDirtyAndChanged(
- rawContactId, callerIsSyncAdapter);
}
// If the new raw contact is inserted by a sync adapter, mark mSyncToMetadataNetWork as true
// so that it can trigger the metadata syncing from the server.
@@ -3988,12 +4006,12 @@
private int deleteDataUsage() {
final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
db.execSQL("UPDATE " + Tables.RAW_CONTACTS + " SET " +
- Contacts.TIMES_CONTACTED + "=0," +
- Contacts.LAST_TIME_CONTACTED + "=NULL");
+ Contacts.RAW_TIMES_CONTACTED + "=0," +
+ Contacts.RAW_LAST_TIME_CONTACTED + "=NULL");
db.execSQL("UPDATE " + Tables.CONTACTS + " SET " +
- Contacts.TIMES_CONTACTED + "=0," +
- Contacts.LAST_TIME_CONTACTED + "=NULL");
+ Contacts.RAW_TIMES_CONTACTED + "=0," +
+ Contacts.RAW_LAST_TIME_CONTACTED + "=NULL");
db.delete(Tables.DATA_USAGE_STAT, null, null);
return 1;
@@ -4051,9 +4069,6 @@
case PROFILE: {
invalidateFastScrollingIndexCache();
count = updateContactOptions(values, selection, selectionArgs, callerIsSyncAdapter);
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4061,9 +4076,6 @@
invalidateFastScrollingIndexCache();
count = updateContactOptions(db, ContentUris.parseId(uri), values,
callerIsSyncAdapter);
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4126,9 +4138,6 @@
invalidateFastScrollingIndexCache();
selection = appendAccountIdToSelection(uri, selection);
count = updateRawContacts(values, selection, selectionArgs, callerIsSyncAdapter);
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4145,9 +4154,6 @@
count = updateRawContacts(values, RawContacts._ID + "=?", mSelectionArgs1,
callerIsSyncAdapter);
}
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4173,12 +4179,9 @@
}
case AGGREGATION_EXCEPTIONS: {
- count = updateAggregationException(db, values, callerIsSyncAdapter,
+ count = updateAggregationException(db, values,
/* callerIsMetadataSyncAdapter =*/false);
invalidateFastScrollingIndexCache();
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4244,10 +4247,7 @@
}
case DATA_USAGE_FEEDBACK_ID: {
- count = handleDataUsageFeedback(uri, callerIsSyncAdapter) ? 1 : 0;
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
+ count = handleDataUsageFeedback(uri) ? 1 : 0;
break;
}
@@ -4516,11 +4516,42 @@
return count;
}
+ /**
+ * Used for insert/update raw_contacts/contacts to adjust TIMES_CONTACTED and
+ * LAST_TIME_CONTACTED.
+ */
+ private ContentValues fixUpUsageColumnsForEdit(ContentValues cv) {
+ if (!cv.containsKey(Contacts.LR_LAST_TIME_CONTACTED)
+ && !cv.containsKey(Contacts.LR_TIMES_CONTACTED)) {
+ return cv;
+ }
+ final ContentValues ret = new ContentValues(cv);
+
+ ContactsDatabaseHelper.copyLongValue(
+ ret, Contacts.RAW_LAST_TIME_CONTACTED,
+ ret, Contacts.LR_LAST_TIME_CONTACTED);
+ if (ret.containsKey(Contacts.LR_TIMES_CONTACTED)) {
+ getDatabaseHelper().logWtf(
+ "Column '" + Contacts.LR_TIMES_CONTACTED + "' can no longer be modified"
+ + " directly. Caller=" + getCallingPackage());
+ }
+
+ ret.remove(Contacts.LR_LAST_TIME_CONTACTED);
+ ret.remove(Contacts.LR_TIMES_CONTACTED);
+ return ret;
+ }
+
private int updateRawContact(SQLiteDatabase db, long rawContactId, ContentValues values,
boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
final String selection = RawContactsColumns.CONCRETE_ID + " = ?";
mSelectionArgs1[0] = Long.toString(rawContactId);
+ values = fixUpUsageColumnsForEdit(values);
+
+ if (values.size() == 0) {
+ return 0; // Nothing to update; bail out.
+ }
+
final ContactsDatabaseHelper dbHelper = mDbHelper.get();
final boolean requestUndoDelete = flagIsClear(values, RawContacts.DELETED);
@@ -4596,8 +4627,6 @@
if (shouldMarkMetadataDirtyForRawContact(values)) {
mTransactionContext.get().markRawContactMetadataDirty(
rawContactId, callerIsMetadataSyncAdapter);
- mTransactionContext.get().markRawContactDirtyAndChanged(
- rawContactId, callerIsSyncAdapter);
}
if (isBackupIdChanging) {
Cursor cursor = db.query(Tables.RAW_CONTACTS,
@@ -4755,6 +4784,8 @@
private int updateContactOptions(
SQLiteDatabase db, long contactId, ContentValues inputValues, boolean callerIsSyncAdapter) {
+ inputValues = fixUpUsageColumnsForEdit(inputValues);
+
final ContentValues values = new ContentValues();
ContactsDatabaseHelper.copyStringValue(
values, RawContacts.CUSTOM_RINGTONE,
@@ -4763,11 +4794,11 @@
values, RawContacts.SEND_TO_VOICEMAIL,
inputValues, Contacts.SEND_TO_VOICEMAIL);
ContactsDatabaseHelper.copyLongValue(
- values, RawContacts.LAST_TIME_CONTACTED,
- inputValues, Contacts.LAST_TIME_CONTACTED);
+ values, RawContacts.RAW_LAST_TIME_CONTACTED,
+ inputValues, Contacts.RAW_LAST_TIME_CONTACTED);
ContactsDatabaseHelper.copyLongValue(
- values, RawContacts.TIMES_CONTACTED,
- inputValues, Contacts.TIMES_CONTACTED);
+ values, RawContacts.RAW_TIMES_CONTACTED,
+ inputValues, Contacts.RAW_TIMES_CONTACTED);
ContactsDatabaseHelper.copyLongValue(
values, RawContacts.STARRED,
inputValues, Contacts.STARRED);
@@ -4782,9 +4813,11 @@
final boolean hasStarredValue = flagExists(values, RawContacts.STARRED);
final boolean hasPinnedValue = flagExists(values, RawContacts.PINNED);
final boolean hasVoiceMailValue = flagExists(values, RawContacts.SEND_TO_VOICEMAIL);
- if (hasStarredValue || hasPinnedValue || hasVoiceMailValue) {
+ if (hasStarredValue) {
// Mark dirty when changing starred to trigger sync.
values.put(RawContacts.DIRTY, 1);
+ }
+ if (mMetadataSyncEnabled && (hasStarredValue || hasPinnedValue || hasVoiceMailValue)) {
// Mark dirty to trigger metadata syncing.
values.put(RawContacts.METADATA_DIRTY, 1);
}
@@ -4825,11 +4858,11 @@
values, RawContacts.SEND_TO_VOICEMAIL,
inputValues, Contacts.SEND_TO_VOICEMAIL);
ContactsDatabaseHelper.copyLongValue(
- values, RawContacts.LAST_TIME_CONTACTED,
- inputValues, Contacts.LAST_TIME_CONTACTED);
+ values, RawContacts.RAW_LAST_TIME_CONTACTED,
+ inputValues, Contacts.RAW_LAST_TIME_CONTACTED);
ContactsDatabaseHelper.copyLongValue(
- values, RawContacts.TIMES_CONTACTED,
- inputValues, Contacts.TIMES_CONTACTED);
+ values, RawContacts.RAW_TIMES_CONTACTED,
+ inputValues, Contacts.RAW_TIMES_CONTACTED);
ContactsDatabaseHelper.copyLongValue(
values, RawContacts.STARRED,
inputValues, Contacts.STARRED);
@@ -4843,8 +4876,8 @@
int rslt = db.update(Tables.CONTACTS, values, Contacts._ID + "=?",
mSelectionArgs1);
- if (inputValues.containsKey(Contacts.LAST_TIME_CONTACTED) &&
- !inputValues.containsKey(Contacts.TIMES_CONTACTED)) {
+ if (inputValues.containsKey(Contacts.RAW_LAST_TIME_CONTACTED) &&
+ !inputValues.containsKey(Contacts.RAW_TIMES_CONTACTED)) {
db.execSQL(UPDATE_TIMES_CONTACTED_CONTACTS_TABLE, mSelectionArgs1);
db.execSQL(UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE, mSelectionArgs1);
}
@@ -4852,7 +4885,7 @@
}
private int updateAggregationException(SQLiteDatabase db, ContentValues values,
- boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
+ boolean callerIsMetadataSyncAdapter) {
Integer exceptionType = values.getAsInteger(AggregationExceptions.TYPE);
Long rcId1 = values.getAsLong(AggregationExceptions.RAW_CONTACT_ID1);
Long rcId2 = values.getAsLong(AggregationExceptions.RAW_CONTACT_ID2);
@@ -4896,11 +4929,6 @@
mTransactionContext.get().markRawContactMetadataDirty(rawContactId2,
callerIsMetadataSyncAdapter);
- mTransactionContext.get().markRawContactDirtyAndChanged(rawContactId1,
- callerIsSyncAdapter);
- mTransactionContext.get().markRawContactDirtyAndChanged(rawContactId2,
- callerIsSyncAdapter);
-
// The return value is fake - we just confirm that we made a change, not count actual
// rows changed.
return 1;
@@ -5085,8 +5113,8 @@
ContentValues usageStatsValues = new ContentValues();
usageStatsValues.put(DataUsageStatColumns.DATA_ID, dataId);
usageStatsValues.put(DataUsageStatColumns.USAGE_TYPE_INT, typeInt);
- usageStatsValues.put(DataUsageStatColumns.LAST_TIME_USED, lastTimeUsed);
- usageStatsValues.put(DataUsageStatColumns.TIMES_USED, timesUsed);
+ usageStatsValues.put(DataUsageStatColumns.RAW_LAST_TIME_USED, lastTimeUsed);
+ usageStatsValues.put(DataUsageStatColumns.RAW_TIMES_USED, timesUsed);
updateDataUsageStats(db, usageStatsValues);
}
}
@@ -5108,8 +5136,7 @@
values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
values.put(AggregationExceptions.TYPE, typeInt);
- updateAggregationException(db, values, /*callerIsSyncAdapter=*/true,
- /* callerIsMetadataSyncAdapter =*/true);
+ updateAggregationException(db, values, /* callerIsMetadataSyncAdapter =*/true);
if (rawContactId1 != rawContactId) {
aggregationRawContactIdsInServer.add(rawContactId1);
}
@@ -5127,8 +5154,7 @@
values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
values.put(AggregationExceptions.RAW_CONTACT_ID2, deleteRawContactId);
values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_AUTOMATIC);
- updateAggregationException(db, values, /*callerIsSyncAdapter=*/true,
- /* callerIsMetadataSyncAdapter =*/true);
+ updateAggregationException(db, values, /* callerIsMetadataSyncAdapter =*/true);
}
}
@@ -5488,6 +5514,11 @@
" order=[" + sortOrder + "] CPID=" + Binder.getCallingPid() +
" User=" + UserUtils.getCurrentUserHandle(getContext()));
}
+
+ mContactsHelper.validateProjection(getCallingPackage(), projection);
+ mContactsHelper.validateSql(getCallingPackage(), selection);
+ mContactsHelper.validateSql(getCallingPackage(), sortOrder);
+
waitForAccess(mReadAccessLatch);
if (!isDirectoryParamValid(uri)) {
@@ -5560,6 +5591,35 @@
return new MatrixCursor(projection);
}
+ private String getRealCallerPackageName(Uri queryUri) {
+ // If called by another CP2, then the URI should contain the original package name.
+ if (calledByAnotherSelf()) {
+ final String passedPackage = queryUri.getQueryParameter(
+ Directory.CALLER_PACKAGE_PARAM_KEY);
+ if (TextUtils.isEmpty(passedPackage)) {
+ Log.wtfStack(TAG,
+ "Cross-profile query with no " + Directory.CALLER_PACKAGE_PARAM_KEY);
+ return "UNKNOWN";
+ }
+ return passedPackage;
+ } else {
+ // Otherwise, just return the real calling package name.
+ return getCallingPackage();
+ }
+ }
+
+ /**
+ * Returns true if called by a different user's CP2.
+ */
+ private boolean calledByAnotherSelf() {
+ // Note normally myUid is always different from the callerUid in the code path where
+ // this method is used, except during unit tests, where the caller is always the same
+ // process.
+ final int myUid = android.os.Process.myUid();
+ final int callerUid = Binder.getCallingUid();
+ return (myUid != callerUid) && UserHandle.isSameApp(myUid, callerUid);
+ }
+
private Cursor queryDirectoryAuthority(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder, String directory,
final CancellationSignal cancellationSignal) {
@@ -5579,6 +5639,11 @@
if (directoryInfo.accountType != null) {
builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, directoryInfo.accountType);
}
+ // Pass the caller package name.
+ // Note the request may come from the CP2 on the primary profile. In that case, the
+ // real caller package is passed via the query paramter. See getRealCallerPackageName().
+ builder.appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY,
+ getRealCallerPackageName(uri));
String limit = getLimit(uri);
if (limit != null) {
@@ -5593,6 +5658,14 @@
Cursor cursor;
try {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "Making directory query: uri=" + directoryUri +
+ " projection=" + Arrays.toString(projection) +
+ " selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
+ " order=[" + sortOrder + "]" +
+ " Caller=" + getCallingPackage() +
+ " User=" + UserUtils.getCurrentUserHandle(getContext()));
+ }
cursor = getContext().getContentResolver().query(
directoryUri, projection, selection, selectionArgs, sortOrder);
if (cursor == null) {
@@ -5631,7 +5704,10 @@
throw new IllegalArgumentException(
"Authority " + localUri.getAuthority() + " is not a valid CP2 authority.");
}
- final Uri remoteUri = maybeAddUserId(localUri, corpUserId);
+ // Add the "user-id @" to the URI, and also pass the caller package name.
+ final Uri remoteUri = maybeAddUserId(localUri, corpUserId).buildUpon()
+ .appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY, getCallingPackage())
+ .build();
Cursor cursor = getContext().getContentResolver().query(remoteUri, projection, selection,
selectionArgs, sortOrder, cancellationSignal);
if (cursor == null) {
@@ -5930,8 +6006,8 @@
if (projection != null) {
subProjection = new String[projection.length + 2];
System.arraycopy(projection, 0, subProjection, 0, projection.length);
- subProjection[projection.length + 0] = DataUsageStatColumns.TIMES_USED;
- subProjection[projection.length + 1] = DataUsageStatColumns.LAST_TIME_USED;
+ subProjection[projection.length + 0] = DataUsageStatColumns.LR_TIMES_USED;
+ subProjection[projection.length + 1] = DataUsageStatColumns.LR_LAST_TIME_USED;
}
// String that will store the query for starred contacts. For phone only queries,
@@ -5954,7 +6030,8 @@
// it is included in the list of strequent numbers.
tableBuilder.append("(SELECT * FROM " + Views.DATA + " WHERE "
+ Contacts.STARRED + "=1)" + " AS " + Tables.DATA
- + " LEFT OUTER JOIN " + Tables.DATA_USAGE_STAT
+ + " LEFT OUTER JOIN " + Views.DATA_USAGE_LR
+ + " AS " + Tables.DATA_USAGE_STAT
+ " ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "="
+ DataColumns.CONCRETE_ID + " AND "
+ DataUsageStatColumns.CONCRETE_USAGE_TYPE + "="
@@ -5987,7 +6064,7 @@
// data rows (almost always it should be), and we don't want any phone
// numbers not used by the user. This way sqlite is able to drop a number of
// rows in view_data in the early stage of data lookup.
- tableBuilder.append(Tables.DATA_USAGE_STAT
+ tableBuilder.append(Views.DATA_USAGE_LR + " AS " + Tables.DATA_USAGE_STAT
+ " INNER JOIN " + Views.DATA + " " + Tables.DATA
+ " ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "="
+ DataColumns.CONCRETE_ID + " AND "
@@ -6040,7 +6117,7 @@
// Phone numbers that were used more than 30 days ago are dropped from frequents
final String frequentQuery = "SELECT * FROM (" + frequentInnerQuery + ") WHERE " +
- TIME_SINCE_LAST_USED_SEC + "<" + LAST_TIME_USED_30_DAYS_SEC;
+ LR_TIME_SINCE_LAST_USED_SEC + "<" + LAST_TIME_USED_30_DAYS_SEC;
final String starredQuery = "SELECT * FROM (" + starredInnerQuery + ")";
// Put them together
@@ -6992,8 +7069,9 @@
providerStatus = ProviderStatus.STATUS_EMPTY;
}
return buildSingleRowResult(projection,
- new String[] {ProviderStatus.STATUS},
- new Object[] {providerStatus});
+ new String[] {ProviderStatus.STATUS,
+ ProviderStatus.DATABASE_CREATION_TIMESTAMP},
+ new Object[] {providerStatus, mDbHelper.get().getDatabaseCreationTime()});
}
case DIRECTORIES : {
@@ -7960,7 +8038,7 @@
if (includeDataUsageStat) {
sb.append(" ON (" +
DbQueryUtils.concatenateClauses(
- DataUsageStatColumns.CONCRETE_TIMES_USED + " > 0",
+ DataUsageStatColumns.CONCRETE_RAW_TIMES_USED + " > 0",
RawContacts.CONTACT_ID + "=" + Views.CONTACTS + "." + Contacts._ID) +
")");
}
@@ -8347,7 +8425,8 @@
private void appendDataUsageStatJoin(StringBuilder sb, int usageType, String dataIdColumn) {
if (usageType != USAGE_TYPE_ALL) {
- sb.append(" LEFT OUTER JOIN " + Tables.DATA_USAGE_STAT +
+ sb.append(" LEFT OUTER JOIN " + Views.DATA_USAGE_LR +
+ " as " + Tables.DATA_USAGE_STAT +
" ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "=");
sb.append(dataIdColumn);
sb.append(" AND " + DataUsageStatColumns.CONCRETE_USAGE_TYPE + "=");
@@ -8357,13 +8436,21 @@
sb.append(
" LEFT OUTER JOIN " +
"(SELECT " +
- DataUsageStatColumns.CONCRETE_DATA_ID + " as STAT_DATA_ID, " +
- "SUM(" + DataUsageStatColumns.CONCRETE_TIMES_USED +
- ") as " + DataUsageStatColumns.TIMES_USED + ", " +
- "MAX(" + DataUsageStatColumns.CONCRETE_LAST_TIME_USED +
- ") as " + DataUsageStatColumns.LAST_TIME_USED +
- " FROM " + Tables.DATA_USAGE_STAT + " GROUP BY " +
- DataUsageStatColumns.CONCRETE_DATA_ID + ") as " + Tables.DATA_USAGE_STAT
+ DataUsageStatColumns.DATA_ID + " as STAT_DATA_ID," +
+ " SUM(ifnull(" + DataUsageStatColumns.RAW_TIMES_USED +
+ ",0)) as " + DataUsageStatColumns.RAW_TIMES_USED + ", " +
+ " MAX(ifnull(" + DataUsageStatColumns.RAW_LAST_TIME_USED +
+ ",0)) as " + DataUsageStatColumns.RAW_LAST_TIME_USED + "," +
+
+ // Note this is not ideal -- we should use "lowres(sum(LR_TIMES_USED))"
+ // here, but for performance reasons we just do it simple.
+ " SUM(ifnull(" + DataUsageStatColumns.LR_TIMES_USED +
+ ",0)) as " + DataUsageStatColumns.LR_TIMES_USED + ", " +
+
+ " MAX(ifnull(" + DataUsageStatColumns.LR_LAST_TIME_USED +
+ ",0)) as " + DataUsageStatColumns.LR_LAST_TIME_USED +
+ " FROM " + Views.DATA_USAGE_LR + " GROUP BY " +
+ DataUsageStatColumns.DATA_ID + ") as " + Tables.DATA_USAGE_STAT
);
sb.append(" ON (STAT_DATA_ID=");
sb.append(dataIdColumn);
@@ -8793,6 +8880,9 @@
}
case PROFILE_AS_VCARD: {
+ if (!mode.equals("r")) {
+ throw new IllegalArgumentException("Write is not supported.");
+ }
// When opening a contact as file, we pass back contents as a
// vCard-encoded stream. We build into a local buffer first,
// then pipe into MemoryFile once the exact size is known.
@@ -8802,6 +8892,9 @@
}
case CONTACTS_AS_VCARD: {
+ if (!mode.equals("r")) {
+ throw new IllegalArgumentException("Write is not supported.");
+ }
// When opening a contact as file, we pass back contents as a
// vCard-encoded stream. We build into a local buffer first,
// then pipe into MemoryFile once the exact size is known.
@@ -8811,6 +8904,9 @@
}
case CONTACTS_AS_MULTI_VCARD: {
+ if (!mode.equals("r")) {
+ throw new IllegalArgumentException("Write is not supported.");
+ }
final String lookupKeys = uri.getPathSegments().get(2);
final String[] lookupKeyList = lookupKeys.split(":");
final StringBuilder inBuilder = new StringBuilder();
@@ -8856,7 +8952,8 @@
default:
throw new FileNotFoundException(
- mDbHelper.get().exceptionMessage("File does not exist", uri));
+ mDbHelper.get().exceptionMessage(
+ "Stream I/O not supported on this URI.", uri));
}
}
@@ -9274,6 +9371,8 @@
return StreamItems.StreamItemPhotos.CONTENT_ITEM_TYPE;
case STREAM_ITEMS_PHOTOS:
throw new UnsupportedOperationException("Not supported for write-only URI " + uri);
+ case PROVIDER_STATUS:
+ return ProviderStatus.CONTENT_TYPE;
default:
waitForAccess(mReadAccessLatch);
return mLegacyApiSupport.getType(uri);
@@ -9678,7 +9777,7 @@
// just bump the aggregation algorithm version and let the provider start normally.
try {
final SQLiteDatabase db = mContactsHelper.getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
updateAggregationAlgorithmVersion();
db.setTransactionSuccessful();
@@ -9731,7 +9830,7 @@
db.execSQL(UNDEMOTE_RAW_CONTACT, arg);
}
- private boolean handleDataUsageFeedback(Uri uri, boolean callerIsSyncAdapter) {
+ private boolean handleDataUsageFeedback(Uri uri) {
final long currentTimeMillis = Clock.getInstance().currentTimeMillis();
final String usageType = uri.getQueryParameter(DataUsageFeedback.USAGE_TYPE);
final String[] ids = uri.getLastPathSegment().trim().split(",");
@@ -9770,7 +9869,6 @@
final long rid = cursor.getLong(0);
mTransactionContext.get().markRawContactMetadataDirty(rid,
/* isMetadataSyncAdapter =*/false);
- mTransactionContext.get().markRawContactDirtyAndChanged(rid, callerIsSyncAdapter);
rawContactIds.add(rid);
}
} finally {
@@ -9781,15 +9879,15 @@
final String rids = TextUtils.join(",", rawContactIds);
db.execSQL("UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContacts.LAST_TIME_CONTACTED + "=?" +
- "," + RawContacts.TIMES_CONTACTED + "=" +
- "ifnull(" + RawContacts.TIMES_CONTACTED + ",0) + 1" +
+ " SET " + RawContacts.RAW_LAST_TIME_CONTACTED + "=?" +
+ "," + RawContacts.RAW_TIMES_CONTACTED + "=" +
+ "ifnull(" + RawContacts.RAW_TIMES_CONTACTED + ",0) + 1" +
" WHERE " + RawContacts._ID + " IN (" + rids + ")"
, mSelectionArgs1);
db.execSQL("UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.LAST_TIME_CONTACTED + "=?1" +
- "," + Contacts.TIMES_CONTACTED + "=" +
- "ifnull(" + Contacts.TIMES_CONTACTED + ",0) + 1" +
+ " SET " + Contacts.RAW_LAST_TIME_CONTACTED + "=?1" +
+ "," + Contacts.RAW_TIMES_CONTACTED + "=" +
+ "ifnull(" + Contacts.RAW_TIMES_CONTACTED + ",0) + 1" +
"," + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + "=?1" +
" WHERE " + Contacts._ID + " IN (SELECT " + RawContacts.CONTACT_ID +
" FROM " + Tables.RAW_CONTACTS +
@@ -9836,9 +9934,9 @@
mSelectionArgs2[1] = String.valueOf(id);
db.execSQL("UPDATE " + Tables.DATA_USAGE_STAT +
- " SET " + DataUsageStatColumns.TIMES_USED + "=" +
- "ifnull(" + DataUsageStatColumns.TIMES_USED +",0)+1" +
- "," + DataUsageStatColumns.LAST_TIME_USED + "=?" +
+ " SET " + DataUsageStatColumns.RAW_TIMES_USED + "=" +
+ "ifnull(" + DataUsageStatColumns.RAW_TIMES_USED +",0)+1" +
+ "," + DataUsageStatColumns.RAW_LAST_TIME_USED + "=?" +
" WHERE " + DataUsageStatColumns._ID + "=?",
mSelectionArgs2);
} else {
@@ -9849,8 +9947,8 @@
db.execSQL("INSERT INTO " + Tables.DATA_USAGE_STAT +
"(" + DataUsageStatColumns.DATA_ID +
"," + DataUsageStatColumns.USAGE_TYPE_INT +
- "," + DataUsageStatColumns.TIMES_USED +
- "," + DataUsageStatColumns.LAST_TIME_USED +
+ "," + DataUsageStatColumns.RAW_TIMES_USED +
+ "," + DataUsageStatColumns.RAW_LAST_TIME_USED +
") VALUES (?,?,?,?)",
mSelectionArgs4);
}
@@ -9863,14 +9961,14 @@
}
/**
- * Update {@link Tables#DATA_USAGE_STAT}.
+ * Directly update {@link Tables#DATA_USAGE_STAT}; used for metadata sync.
* Update or insert usageType, lastTimeUsed, and timesUsed for specific dataId.
*/
private void updateDataUsageStats(SQLiteDatabase db, ContentValues values) {
final String dataId = values.getAsString(DataUsageStatColumns.DATA_ID);
final String type = values.getAsString(DataUsageStatColumns.USAGE_TYPE_INT);
- final String lastTimeUsed = values.getAsString(DataUsageStatColumns.LAST_TIME_USED);
- final String timesUsed = values.getAsString(DataUsageStatColumns.TIMES_USED);
+ final String lastTimeUsed = values.getAsString(DataUsageStatColumns.RAW_LAST_TIME_USED);
+ final String timesUsed = values.getAsString(DataUsageStatColumns.RAW_TIMES_USED);
mSelectionArgs2[0] = dataId;
mSelectionArgs2[1] = type;
@@ -9886,8 +9984,8 @@
mSelectionArgs3[1] = timesUsed;
mSelectionArgs3[2] = String.valueOf(id);
db.execSQL("UPDATE " + Tables.DATA_USAGE_STAT +
- " SET " + DataUsageStatColumns.LAST_TIME_USED + "=?" +
- "," + DataUsageStatColumns.TIMES_USED + "=?" +
+ " SET " + DataUsageStatColumns.RAW_LAST_TIME_USED + "=?" +
+ "," + DataUsageStatColumns.RAW_TIMES_USED + "=?" +
" WHERE " + DataUsageStatColumns._ID + "=?",
mSelectionArgs3);
} else {
@@ -9898,8 +9996,8 @@
db.execSQL("INSERT INTO " + Tables.DATA_USAGE_STAT +
"(" + DataUsageStatColumns.DATA_ID +
"," + DataUsageStatColumns.USAGE_TYPE_INT +
- "," + DataUsageStatColumns.TIMES_USED +
- "," + DataUsageStatColumns.LAST_TIME_USED +
+ "," + DataUsageStatColumns.RAW_TIMES_USED +
+ "," + DataUsageStatColumns.RAW_LAST_TIME_USED +
") VALUES (?,?,?,?)",
mSelectionArgs4);
}
@@ -10120,4 +10218,25 @@
public void switchToProfileModeForTest() {
switchToProfileMode();
}
+
+ @Override
+ public void shutdown() {
+ if (mBackgroundHandler != null) {
+ mBackgroundHandler.getLooper().quit();
+ try {
+ mBackgroundThread.join();
+ } catch (InterruptedException ignore) {
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public ContactsDatabaseHelper getContactsDatabaseHelperForTest() {
+ return mContactsHelper;
+ }
+
+ @VisibleForTesting
+ public ProfileProvider getProfileProviderForTest() {
+ return mProfileProvider;
+ }
}
diff --git a/src/com/android/providers/contacts/ContactsTransaction.java b/src/com/android/providers/contacts/ContactsTransaction.java
index c6c11d9..e220dd9 100644
--- a/src/com/android/providers/contacts/ContactsTransaction.java
+++ b/src/com/android/providers/contacts/ContactsTransaction.java
@@ -113,9 +113,9 @@
mDatabasesForTransaction.add(0, db);
mDatabaseTagMap.put(tag, db);
if (listener != null) {
- db.beginTransactionWithListener(listener);
+ db.beginTransactionWithListenerNonExclusive(listener);
} else {
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
}
}
}
diff --git a/src/com/android/providers/contacts/ContactsUpgradeReceiver.java b/src/com/android/providers/contacts/ContactsUpgradeReceiver.java
index 57c0cd0..8259c8d 100644
--- a/src/com/android/providers/contacts/ContactsUpgradeReceiver.java
+++ b/src/com/android/providers/contacts/ContactsUpgradeReceiver.java
@@ -16,7 +16,6 @@
package com.android.providers.contacts;
-import android.app.ActivityManagerNative;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
diff --git a/src/com/android/providers/contacts/GlobalSearchSupport.java b/src/com/android/providers/contacts/GlobalSearchSupport.java
index 20307d4..6678b95 100644
--- a/src/com/android/providers/contacts/GlobalSearchSupport.java
+++ b/src/com/android/providers/contacts/GlobalSearchSupport.java
@@ -232,7 +232,7 @@
+ Contacts.PHOTO_THUMBNAIL_URI + ", "
+ Contacts.DISPLAY_NAME + ", "
+ PRESENCE_SQL + " AS " + Contacts.CONTACT_PRESENCE + ", "
- + Contacts.LAST_TIME_CONTACTED);
+ + Contacts.LR_LAST_TIME_CONTACTED);
if (haveFilter) {
sb.append(", " + SearchSnippets.SNIPPET);
}
diff --git a/src/com/android/providers/contacts/LegacyApiSupport.java b/src/com/android/providers/contacts/LegacyApiSupport.java
index 598a4a0..741639a 100644
--- a/src/com/android/providers/contacts/LegacyApiSupport.java
+++ b/src/com/android/providers/contacts/LegacyApiSupport.java
@@ -107,10 +107,6 @@
private static final int SEARCH_SUGGESTIONS = 32;
private static final int SEARCH_SHORTCUT = 33;
private static final int PHONES_FILTER = 34;
- private static final int LIVE_FOLDERS_PEOPLE = 35;
- private static final int LIVE_FOLDERS_PEOPLE_GROUP_NAME = 36;
- private static final int LIVE_FOLDERS_PEOPLE_WITH_PHONES = 37;
- private static final int LIVE_FOLDERS_PEOPLE_FAVORITES = 38;
private static final int CONTACTMETHODS_EMAIL = 39;
private static final int GROUP_NAME_MEMBERS = 40;
private static final int GROUP_SYSTEM_ID_MEMBERS = 41;
@@ -185,24 +181,6 @@
+ " ELSE " + Tables.DATA + "." + Email.DATA
+ " END)";
- private static final Uri LIVE_FOLDERS_CONTACTS_URI = Uri.withAppendedPath(
- ContactsContract.AUTHORITY_URI, "live_folders/contacts");
-
- private static final Uri LIVE_FOLDERS_CONTACTS_WITH_PHONES_URI = Uri.withAppendedPath(
- ContactsContract.AUTHORITY_URI, "live_folders/contacts_with_phones");
-
- private static final Uri LIVE_FOLDERS_CONTACTS_FAVORITES_URI = Uri.withAppendedPath(
- ContactsContract.AUTHORITY_URI, "live_folders/favorites");
-
- private static final String CONTACTS_UPDATE_LASTTIMECONTACTED =
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.LAST_TIME_CONTACTED + "=? " +
- "WHERE " + Contacts._ID + "=?";
- private static final String RAWCONTACTS_UPDATE_LASTTIMECONTACTED =
- "UPDATE " + Tables.RAW_CONTACTS + " SET "
- + RawContacts.LAST_TIME_CONTACTED + "=? WHERE "
- + RawContacts._ID + "=?";
-
private String[] mSelectionArgs1 = new String[1];
private String[] mSelectionArgs2 = new String[2];
@@ -357,15 +335,6 @@
SEARCH_SHORTCUT);
matcher.addURI(authority, "settings", SETTINGS);
- matcher.addURI(authority, "live_folders/people", LIVE_FOLDERS_PEOPLE);
- matcher.addURI(authority, "live_folders/people/*",
- LIVE_FOLDERS_PEOPLE_GROUP_NAME);
- matcher.addURI(authority, "live_folders/people_with_phones",
- LIVE_FOLDERS_PEOPLE_WITH_PHONES);
- matcher.addURI(authority, "live_folders/favorites",
- LIVE_FOLDERS_PEOPLE_FAVORITES);
-
-
HashMap<String, String> peopleProjectionMap = new HashMap<String, String>();
peopleProjectionMap.put(People.NAME, People.NAME);
peopleProjectionMap.put(People.DISPLAY_NAME, People.DISPLAY_NAME);
@@ -568,10 +537,12 @@
+ " AS " + People.NOTES + ", " +
AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
- Tables.RAW_CONTACTS + "." + RawContacts.TIMES_CONTACTED
- + " AS " + People.TIMES_CONTACTED + ", " +
- Tables.RAW_CONTACTS + "." + RawContacts.LAST_TIME_CONTACTED
- + " AS " + People.LAST_TIME_CONTACTED + ", " +
+
+ // We no longer return even low-res values from CP1.
+ // Note if we just use the value 0 below, certain seletion wouldn't work.
+ "cast(0 as int) AS " + People.TIMES_CONTACTED + ", " +
+ "cast(0 as int) AS " + People.LAST_TIME_CONTACTED + ", " +
+
Tables.RAW_CONTACTS + "." + RawContacts.CUSTOM_RINGTONE
+ " AS " + People.CUSTOM_RINGTONE + ", " +
Tables.RAW_CONTACTS + "." + RawContacts.SEND_TO_VOICEMAIL
@@ -948,7 +919,7 @@
int count = 0;
switch(match) {
case PEOPLE_UPDATE_CONTACT_TIME: {
- count = updateContactTime(uri, values);
+ count = 0; // No longer supported.
break;
}
@@ -1077,11 +1048,6 @@
}
}
- if (values.containsKey(People.LAST_TIME_CONTACTED) &&
- !values.containsKey(People.TIMES_CONTACTED)) {
- updateContactTime(rawContactId, values);
- }
-
return count;
}
@@ -1143,35 +1109,6 @@
Groups._ID + "=" + groupId, null);
}
- private int updateContactTime(Uri uri, ContentValues values) {
- long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
- updateContactTime(rawContactId, values);
- return 1;
- }
-
- private void updateContactTime(long rawContactId, ContentValues values) {
- final Long storedTimeContacted = values.getAsLong(People.LAST_TIME_CONTACTED);
- final long lastTimeContacted = storedTimeContacted != null ?
- storedTimeContacted : System.currentTimeMillis();
-
- // TODO check sanctions
- long contactId = mDbHelper.getContactId(rawContactId);
- SQLiteDatabase mDb = mDbHelper.getWritableDatabase();
- mSelectionArgs2[0] = String.valueOf(lastTimeContacted);
- if (contactId != 0) {
- mSelectionArgs2[1] = String.valueOf(contactId);
- mDb.execSQL(CONTACTS_UPDATE_LASTTIMECONTACTED, mSelectionArgs2);
- // increment times_contacted column
- mSelectionArgs1[0] = String.valueOf(contactId);
- mDb.execSQL(ContactsProvider2.UPDATE_TIMES_CONTACTED_CONTACTS_TABLE, mSelectionArgs1);
- }
- mSelectionArgs2[1] = String.valueOf(rawContactId);
- mDb.execSQL(RAWCONTACTS_UPDATE_LASTTIMECONTACTED, mSelectionArgs2);
- // increment times_contacted column
- mSelectionArgs1[0] = String.valueOf(contactId);
- mDb.execSQL(ContactsProvider2.UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE, mSelectionArgs1);
- }
-
private int updatePhoto(long rawContactId, ContentValues values) {
// TODO check sanctions
@@ -1359,10 +1296,12 @@
values, People.CUSTOM_RINGTONE);
ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.SEND_TO_VOICEMAIL,
values, People.SEND_TO_VOICEMAIL);
- ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.LAST_TIME_CONTACTED,
- values, People.LAST_TIME_CONTACTED);
- ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.TIMES_CONTACTED,
- values, People.TIMES_CONTACTED);
+
+ // We no longer support the following fields in CP1.
+ // ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.LAST_TIME_CONTACTED,
+ // values, People.LAST_TIME_CONTACTED);
+ // ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.TIMES_CONTACTED,
+ // values, People.TIMES_CONTACTED);
ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.STARRED,
values, People.STARRED);
if (mAccount != null) {
@@ -1884,23 +1823,6 @@
db, projection, lookupKey, filter, null);
}
- case LIVE_FOLDERS_PEOPLE:
- return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_URI,
- projection, selection, selectionArgs, sortOrder);
-
- case LIVE_FOLDERS_PEOPLE_WITH_PHONES:
- return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_WITH_PHONES_URI,
- projection, selection, selectionArgs, sortOrder);
-
- case LIVE_FOLDERS_PEOPLE_FAVORITES:
- return mContactsProvider.query(LIVE_FOLDERS_CONTACTS_FAVORITES_URI,
- projection, selection, selectionArgs, sortOrder);
-
- case LIVE_FOLDERS_PEOPLE_GROUP_NAME:
- return mContactsProvider.query(Uri.withAppendedPath(LIVE_FOLDERS_CONTACTS_URI,
- Uri.encode(uri.getLastPathSegment())),
- projection, selection, selectionArgs, sortOrder);
-
case DELETED_PEOPLE:
case DELETED_GROUPS:
throw new UnsupportedOperationException(mDbHelper.exceptionMessage(uri));
@@ -1985,7 +1907,6 @@
* a group with a particular system id. The projection map of the query must include
* {@link People#_ID}.
*
- * @param groupName The name of the group
* @return The where clause.
*/
private String buildGroupSystemIdMatchWhereClause(String systemId) {
diff --git a/src/com/android/providers/contacts/PackageIntentReceiver.java b/src/com/android/providers/contacts/PackageIntentReceiver.java
index 5736249..f07d39b 100644
--- a/src/com/android/providers/contacts/PackageIntentReceiver.java
+++ b/src/com/android/providers/contacts/PackageIntentReceiver.java
@@ -33,6 +33,9 @@
@Override
public void onReceive(Context context, Intent intent) {
Uri packageUri = intent.getData();
+ if (packageUri == null) {
+ return;
+ }
String packageName = packageUri.getSchemeSpecificPart();
IContentProvider iprovider =
context.getContentResolver().acquireProvider(ContactsContract.AUTHORITY);
@@ -44,7 +47,7 @@
}
private void handlePackageChangedForVoicemail(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED) &&
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction()) &&
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
// Forward the intent to the cleanup service for handling the event.
Intent intentToForward = new Intent(context, VoicemailCleanupService.class);
diff --git a/src/com/android/providers/contacts/ProfileAwareUriMatcher.java b/src/com/android/providers/contacts/ProfileAwareUriMatcher.java
index ee5ec03..744addc 100644
--- a/src/com/android/providers/contacts/ProfileAwareUriMatcher.java
+++ b/src/com/android/providers/contacts/ProfileAwareUriMatcher.java
@@ -123,6 +123,9 @@
}
} else if (PROFILE_URI_LOOKUP_KEY_MAP.containsKey(match)) {
int lookupKeySegment = PROFILE_URI_LOOKUP_KEY_MAP.get(match);
+ if (lookupKeySegment >= uri.getPathSegments().size()) {
+ return false;
+ }
String lookupKey = uri.getPathSegments().get(lookupKeySegment);
if (ContactLookupKey.PROFILE_LOOKUP_KEY.equals(lookupKey)) {
return true;
diff --git a/src/com/android/providers/contacts/ProfileDatabaseHelper.java b/src/com/android/providers/contacts/ProfileDatabaseHelper.java
index a23e521..966ee7e 100644
--- a/src/com/android/providers/contacts/ProfileDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ProfileDatabaseHelper.java
@@ -42,18 +42,20 @@
* Returns a new instance for unit tests.
*/
@NeededForTesting
- public static ProfileDatabaseHelper getNewInstanceForTest(Context context) {
- return new ProfileDatabaseHelper(context, null, false);
+ public static ProfileDatabaseHelper getNewInstanceForTest(Context context, String filename) {
+ return new ProfileDatabaseHelper(context, filename, false, /* isTestInstance=*/ true);
}
private ProfileDatabaseHelper(
- Context context, String databaseName, boolean optimizationEnabled) {
- super(context, databaseName, optimizationEnabled);
+ Context context, String databaseName, boolean optimizationEnabled,
+ boolean isTestInstance) {
+ super(context, databaseName, optimizationEnabled, isTestInstance);
}
public static synchronized ProfileDatabaseHelper getInstance(Context context) {
if (sSingleton == null) {
- sSingleton = new ProfileDatabaseHelper(context, DATABASE_NAME, true);
+ sSingleton = new ProfileDatabaseHelper(context, DATABASE_NAME, true,
+ /* isTestInstance=*/ false);
}
return sSingleton;
}
@@ -72,4 +74,18 @@
db.insert(SEQUENCE_TABLE, null, values);
}
}
+
+ @Override
+ protected void postOnCreate() {
+ }
+
+ @Override
+ protected void setDatabaseCreationTime(SQLiteDatabase db) {
+ // We don't need the creation time for the profile DB.
+ }
+
+ @Override
+ protected void loadDatabaseCreationTime(SQLiteDatabase db) {
+ // We don't need the creation time for the profile DB.
+ }
}
diff --git a/src/com/android/providers/contacts/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java
index 88ae4c3..00e7715 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -48,10 +48,14 @@
}
@Override
- protected ProfileDatabaseHelper getDatabaseHelper(Context context) {
+ protected ProfileDatabaseHelper newDatabaseHelper(Context context) {
return ProfileDatabaseHelper.getInstance(context);
}
+ public ProfileDatabaseHelper getDatabaseHelper() {
+ return (ProfileDatabaseHelper) super.getDatabaseHelper();
+ }
+
@Override
protected ThreadLocal<ContactsTransaction> getTransactionHolder() {
return mDelegate.getTransactionHolder();
@@ -149,6 +153,9 @@
private void sendProfileChangedBroadcast() {
final Intent intent = new Intent(Intents.ACTION_PROFILE_CHANGED);
mDelegate.getContext().sendBroadcast(intent, READ_CONTACTS_PERMISSION);
+ // TODO b/35323708 update user profile data here instead of notifying Settings
+ intent.setPackage("com.android.settings");
+ mDelegate.getContext().sendBroadcast(intent, READ_CONTACTS_PERMISSION);
}
@Override
diff --git a/src/com/android/providers/contacts/SearchIndexManager.java b/src/com/android/providers/contacts/SearchIndexManager.java
index ba2a60d..768fb97 100644
--- a/src/com/android/providers/contacts/SearchIndexManager.java
+++ b/src/com/android/providers/contacts/SearchIndexManager.java
@@ -113,7 +113,7 @@
@Override
public String toString() {
- return "Content: " + mSbContent + "\n Name: " + mSbTokens + "\n Tokens: " + mSbTokens;
+ return "Content: " + mSbContent + "\n Name: " + mSbName + "\n Tokens: " + mSbTokens;
}
public void commit() {
diff --git a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
index 1501138..2a36675 100644
--- a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
@@ -1263,8 +1263,8 @@
+ RawContacts.SOURCE_ID + ","
+ RawContacts.CUSTOM_RINGTONE + ","
+ RawContacts.SEND_TO_VOICEMAIL + ","
- + RawContacts.LAST_TIME_CONTACTED + ","
- + RawContacts.TIMES_CONTACTED + ","
+ + RawContacts.RAW_LAST_TIME_CONTACTED + ","
+ + RawContacts.RAW_TIMES_CONTACTED + ","
+ RawContacts.STARRED + ","
+ RawContacts.PINNED + ","
+ DataColumns.CONCRETE_ID + ","
@@ -1299,8 +1299,8 @@
int SOURCE_ID = 6;
int CUSTOM_RINGTONE = 7;
int SEND_TO_VOICEMAIL = 8;
- int LAST_TIME_CONTACTED = 9;
- int TIMES_CONTACTED = 10;
+ int RAW_LAST_TIME_CONTACTED = 9;
+ int RAW_TIMES_CONTACTED = 10;
int STARRED = 11;
int PINNED = 12;
int DATA_ID = 13;
@@ -1319,8 +1319,8 @@
+ Contacts.PHOTO_FILE_ID + "=?, "
+ Contacts.SEND_TO_VOICEMAIL + "=?, "
+ Contacts.CUSTOM_RINGTONE + "=?, "
- + Contacts.LAST_TIME_CONTACTED + "=?, "
- + Contacts.TIMES_CONTACTED + "=?, "
+ + Contacts.RAW_LAST_TIME_CONTACTED + "=?, "
+ + Contacts.RAW_TIMES_CONTACTED + "=?, "
+ Contacts.STARRED + "=?, "
+ Contacts.PINNED + "=?, "
+ Contacts.HAS_PHONE_NUMBER + "=?, "
@@ -1335,8 +1335,8 @@
+ Contacts.PHOTO_FILE_ID + ", "
+ Contacts.SEND_TO_VOICEMAIL + ", "
+ Contacts.CUSTOM_RINGTONE + ", "
- + Contacts.LAST_TIME_CONTACTED + ", "
- + Contacts.TIMES_CONTACTED + ", "
+ + Contacts.RAW_LAST_TIME_CONTACTED + ", "
+ + Contacts.RAW_TIMES_CONTACTED + ", "
+ Contacts.STARRED + ", "
+ Contacts.PINNED + ", "
+ Contacts.HAS_PHONE_NUMBER + ", "
@@ -1350,8 +1350,8 @@
int PHOTO_FILE_ID = 3;
int SEND_TO_VOICEMAIL = 4;
int CUSTOM_RINGTONE = 5;
- int LAST_TIME_CONTACTED = 6;
- int TIMES_CONTACTED = 7;
+ int RAW_LAST_TIME_CONTACTED = 6;
+ int RAW_TIMES_CONTACTED = 7;
int STARRED = 8;
int PINNED = 9;
int HAS_PHONE_NUMBER = 10;
@@ -1439,12 +1439,12 @@
contactCustomRingtone = c.getString(RawContactsQuery.CUSTOM_RINGTONE);
}
- long lastTimeContacted = c.getLong(RawContactsQuery.LAST_TIME_CONTACTED);
+ long lastTimeContacted = c.getLong(RawContactsQuery.RAW_LAST_TIME_CONTACTED);
if (lastTimeContacted > contactLastTimeContacted) {
contactLastTimeContacted = lastTimeContacted;
}
- int timesContacted = c.getInt(RawContactsQuery.TIMES_CONTACTED);
+ int timesContacted = c.getInt(RawContactsQuery.RAW_TIMES_CONTACTED);
if (timesContacted > contactTimesContacted) {
contactTimesContacted = timesContacted;
}
@@ -1523,9 +1523,9 @@
totalRowCount == contactSendToVoicemail ? 1 : 0);
DatabaseUtils.bindObjectToProgram(statement, ContactReplaceSqlStatement.CUSTOM_RINGTONE,
contactCustomRingtone);
- statement.bindLong(ContactReplaceSqlStatement.LAST_TIME_CONTACTED,
+ statement.bindLong(ContactReplaceSqlStatement.RAW_LAST_TIME_CONTACTED,
contactLastTimeContacted);
- statement.bindLong(ContactReplaceSqlStatement.TIMES_CONTACTED,
+ statement.bindLong(ContactReplaceSqlStatement.RAW_TIMES_CONTACTED,
contactTimesContacted);
statement.bindLong(ContactReplaceSqlStatement.STARRED,
contactStarred);
diff --git a/src/com/android/providers/contacts/sqlite/DatabaseAnalyzer.java b/src/com/android/providers/contacts/sqlite/DatabaseAnalyzer.java
new file mode 100644
index 0000000..9a03aaf
--- /dev/null
+++ b/src/com/android/providers/contacts/sqlite/DatabaseAnalyzer.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts.sqlite;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import com.android.providers.contacts.AbstractContactsProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to extract table/view/column names from databases.
+ */
+public class DatabaseAnalyzer {
+ private static final String TAG = "DatabaseAnalyzer";
+
+ private static final boolean VERBOSE_LOGGING = AbstractContactsProvider.VERBOSE_LOGGING;
+
+ private DatabaseAnalyzer() {
+ }
+
+ /**
+ * Find and return all table/view names in a db.
+ */
+ private static List<String> findTablesAndViews(SQLiteDatabase db) {
+ final List<String> ret = new ArrayList<>();
+ try (final Cursor c = db.rawQuery(
+ "SELECT name FROM sqlite_master WHERE type in (\"table\", \"view\")", null)) {
+ while (c.moveToNext()) {
+ ret.add(c.getString(0).toLowerCase());
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Find all columns in a table/view.
+ */
+ private static List<String> findColumns(SQLiteDatabase db, String table) {
+ final List<String> ret = new ArrayList<>();
+
+ // Open the table/view but requests 0 rows.
+ final Cursor c = db.rawQuery("SELECT * FROM " + table + " WHERE 0 LIMIT 0", null);
+ try {
+ // Collect the column names.
+ for (int i = 0; i < c.getColumnCount(); i++) {
+ ret.add(c.getColumnName(i).toLowerCase());
+ }
+ } finally {
+ c.close();
+ }
+ return ret;
+ }
+
+ /**
+ * Return all table/view names that clients shouldn't use in their queries -- basically the
+ * result contains all table/view names, except for the names that are column names of any
+ * tables.
+ */
+ public static List<String> findTableViewsAllowingColumns(SQLiteDatabase db) {
+ final List<String> tables = findTablesAndViews(db);
+ if (VERBOSE_LOGGING) {
+ Log.d(TAG, "Tables and views:");
+ }
+ final List<String> ret = new ArrayList<>(tables); // Start with the table/view list.
+ for (String name : tables) {
+ if (VERBOSE_LOGGING) {
+ Log.d(TAG, " " + name);
+ }
+ final List<String> columns = findColumns(db, name);
+ if (VERBOSE_LOGGING) {
+ Log.d(TAG, " Columns: " + columns);
+ }
+ for (String c : columns) {
+ if (ret.remove(c)) {
+ Log.d(TAG, "Removing [" + c + "] from disallow list");
+ }
+ }
+ }
+ return ret;
+ }
+}
diff --git a/src/com/android/providers/contacts/sqlite/SqlChecker.java b/src/com/android/providers/contacts/sqlite/SqlChecker.java
new file mode 100644
index 0000000..2db1f47
--- /dev/null
+++ b/src/com/android/providers/contacts/sqlite/SqlChecker.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.contacts.sqlite;
+
+import android.annotation.Nullable;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.providers.contacts.AbstractContactsProvider;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * Simple SQL validator to detect uses of hidden tables / columns as well as invalid SQLs.
+ */
+public class SqlChecker {
+ private static final String TAG = "SqlChecker";
+
+ private static final String PRIVATE_PREFIX = "x_"; // MUST BE LOWERCASE.
+
+ private static final boolean VERBOSE_LOGGING = AbstractContactsProvider.VERBOSE_LOGGING;
+
+ private final ArraySet<String> mInvalidTokens;
+
+ /**
+ * Create a new instance with given invalid tokens.
+ */
+ public SqlChecker(List<String> invalidTokens) {
+ mInvalidTokens = new ArraySet<>(invalidTokens.size());
+
+ for (int i = invalidTokens.size() - 1; i >= 0; i--) {
+ mInvalidTokens.add(invalidTokens.get(i).toLowerCase());
+ }
+ if (VERBOSE_LOGGING) {
+ Log.d(TAG, "Initialized with invalid tokens: " + invalidTokens);
+ }
+ }
+
+ private static boolean isAlpha(char ch) {
+ return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || (ch == '_');
+ }
+
+ private static boolean isNum(char ch) {
+ return ('0' <= ch && ch <= '9');
+ }
+
+ private static boolean isAlNum(char ch) {
+ return isAlpha(ch) || isNum(ch);
+ }
+
+ private static boolean isAnyOf(char ch, String set) {
+ return set.indexOf(ch) >= 0;
+ }
+
+ /**
+ * Exception for invalid queries.
+ */
+ @VisibleForTesting
+ public static final class InvalidSqlException extends IllegalArgumentException {
+ public InvalidSqlException(String s) {
+ super(s);
+ }
+ }
+
+ private static InvalidSqlException genException(String message, String sql) {
+ throw new InvalidSqlException(message + " in '" + sql + "'");
+ }
+
+ private void throwIfContainsToken(String token, String sql) {
+ final String lower = token.toLowerCase();
+ if (mInvalidTokens.contains(lower) || lower.startsWith(PRIVATE_PREFIX)) {
+ throw genException("Detected disallowed token: " + token, sql);
+ }
+ }
+
+ /**
+ * Ensure {@code sql} is valid and doesn't contain invalid tokens.
+ */
+ public void ensureNoInvalidTokens(@Nullable String sql) {
+ findTokens(sql, OPTION_NONE, token -> throwIfContainsToken(token, sql));
+ }
+
+ /**
+ * Ensure {@code sql} only contains a single, valid token. Use to validate column names
+ * in {@link android.content.ContentValues}.
+ */
+ public void ensureSingleTokenOnly(@Nullable String sql) {
+ final AtomicBoolean tokenFound = new AtomicBoolean();
+
+ findTokens(sql, OPTION_TOKEN_ONLY, token -> {
+ if (tokenFound.get()) {
+ throw genException("Multiple tokens detected", sql);
+ }
+ tokenFound.set(true);
+ throwIfContainsToken(token, sql);
+ });
+ if (!tokenFound.get()) {
+ throw genException("Token not found", sql);
+ }
+ }
+
+ @VisibleForTesting
+ static final int OPTION_NONE = 0;
+
+ @VisibleForTesting
+ static final int OPTION_TOKEN_ONLY = 1 << 0;
+
+ private static char peek(String s, int index) {
+ return index < s.length() ? s.charAt(index) : '\0';
+ }
+
+ /**
+ * SQL Tokenizer specialized to extract tokens from SQL (snippets).
+ *
+ * Based on sqlite3GetToken() in tokenzie.c in SQLite.
+ *
+ * Source for v3.8.6 (which android uses): http://www.sqlite.org/src/artifact/ae45399d6252b4d7
+ * (Latest source as of now: http://www.sqlite.org/src/artifact/78c8085bc7af1922)
+ *
+ * Also draft spec: http://www.sqlite.org/draft/tokenreq.html
+ */
+ @VisibleForTesting
+ static void findTokens(@Nullable String sql, int options, Consumer<String> checker) {
+ if (sql == null) {
+ return;
+ }
+ int pos = 0;
+ final int len = sql.length();
+ while (pos < len) {
+ final char ch = peek(sql, pos);
+
+ // Regular token.
+ if (isAlpha(ch)) {
+ final int start = pos;
+ pos++;
+ while (isAlNum(peek(sql, pos))) {
+ pos++;
+ }
+ final int end = pos;
+
+ final String token = sql.substring(start, end);
+ checker.accept(token);
+
+ continue;
+ }
+
+ // Handle quoted tokens
+ if (isAnyOf(ch, "'\"`")) {
+ final int quoteStart = pos;
+ pos++;
+
+ for (;;) {
+ pos = sql.indexOf(ch, pos);
+ if (pos < 0) {
+ throw genException("Unterminated quote", sql);
+ }
+ if (peek(sql, pos + 1) != ch) {
+ break;
+ }
+ // Quoted quote char -- e.g. "abc""def" is a single string.
+ pos += 2;
+ }
+ final int quoteEnd = pos;
+ pos++;
+
+ if (ch != '\'') {
+ // Extract the token
+ final String tokenUnquoted = sql.substring(quoteStart + 1, quoteEnd);
+
+ final String token;
+
+ // Unquote if needed. i.e. "aa""bb" -> aa"bb
+ if (tokenUnquoted.indexOf(ch) >= 0) {
+ token = tokenUnquoted.replaceAll(
+ String.valueOf(ch) + ch, String.valueOf(ch));
+ } else {
+ token = tokenUnquoted;
+ }
+ checker.accept(token);
+ } else {
+ if ((options &= OPTION_TOKEN_ONLY) != 0) {
+ throw genException("Non-token detected", sql);
+ }
+ }
+ continue;
+ }
+ // Handle tokens enclosed in [...]
+ if (ch == '[') {
+ final int quoteStart = pos;
+ pos++;
+
+ pos = sql.indexOf(']', pos);
+ if (pos < 0) {
+ throw genException("Unterminated quote", sql);
+ }
+ final int quoteEnd = pos;
+ pos++;
+
+ final String token = sql.substring(quoteStart + 1, quoteEnd);
+
+ checker.accept(token);
+ continue;
+ }
+ if ((options &= OPTION_TOKEN_ONLY) != 0) {
+ throw genException("Non-token detected", sql);
+ }
+
+ // Detect comments.
+ if (ch == '-' && peek(sql, pos + 1) == '-') {
+ pos += 2;
+ pos = sql.indexOf('\n', pos);
+ if (pos < 0) {
+ // We disallow strings ending in an inline comment.
+ throw genException("Unterminated comment", sql);
+ }
+ pos++;
+
+ continue;
+ }
+ if (ch == '/' && peek(sql, pos + 1) == '*') {
+ pos += 2;
+ pos = sql.indexOf("*/", pos);
+ if (pos < 0) {
+ throw genException("Unterminated comment", sql);
+ }
+ pos += 2;
+
+ continue;
+ }
+
+ // Semicolon is never allowed.
+ if (ch == ';') {
+ throw genException("Semicolon is not allowed", sql);
+ }
+
+ // For this purpose, we can simply ignore other characters.
+ // (Note it doesn't handle the X'' literal properly and reports this X as a token,
+ // but that should be fine...)
+ pos++;
+ }
+ }
+}
diff --git a/src/com/android/providers/contacts/util/DbQueryUtils.java b/src/com/android/providers/contacts/util/DbQueryUtils.java
index d719313..23c144a 100644
--- a/src/com/android/providers/contacts/util/DbQueryUtils.java
+++ b/src/com/android/providers/contacts/util/DbQueryUtils.java
@@ -125,7 +125,7 @@
public static void escapeLikeValue(StringBuilder sb, String value, char escapeChar) {
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
- if (ch == '%' || ch == '_') {
+ if (ch == '%' || ch == '_' || ch == escapeChar) {
sb.append(escapeChar);
}
sb.append(ch);
diff --git a/test_common/Android.mk b/test_common/Android.mk
new file mode 100644
index 0000000..2965f8a
--- /dev/null
+++ b/test_common/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := \
+ android.test.runner \
+ android-support-test \
+ mockito-target-minus-junit4
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := ContactsProviderTestUtils
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/src/com/android/providers/contacts/testutil/CommonDatabaseUtils.java b/test_common/src/com/android/providers/contacts/testutil/CommonDatabaseUtils.java
similarity index 100%
rename from tests/src/com/android/providers/contacts/testutil/CommonDatabaseUtils.java
rename to test_common/src/com/android/providers/contacts/testutil/CommonDatabaseUtils.java
diff --git a/tests/src/com/android/providers/contacts/testutil/ContactUtil.java b/test_common/src/com/android/providers/contacts/testutil/ContactUtil.java
similarity index 100%
rename from tests/src/com/android/providers/contacts/testutil/ContactUtil.java
rename to test_common/src/com/android/providers/contacts/testutil/ContactUtil.java
diff --git a/tests/src/com/android/providers/contacts/testutil/DataUtil.java b/test_common/src/com/android/providers/contacts/testutil/DataUtil.java
similarity index 98%
rename from tests/src/com/android/providers/contacts/testutil/DataUtil.java
rename to test_common/src/com/android/providers/contacts/testutil/DataUtil.java
index 2afd567..32e7619 100644
--- a/tests/src/com/android/providers/contacts/testutil/DataUtil.java
+++ b/test_common/src/com/android/providers/contacts/testutil/DataUtil.java
@@ -23,7 +23,6 @@
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
-import android.test.mock.MockContentResolver;
/**
* Convenience methods for operating on the Data table.
diff --git a/tests/src/com/android/providers/contacts/testutil/DatabaseAsserts.java b/test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
similarity index 100%
rename from tests/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
rename to test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
diff --git a/tests/src/com/android/providers/contacts/testutil/DeletedContactUtil.java b/test_common/src/com/android/providers/contacts/testutil/DeletedContactUtil.java
similarity index 100%
rename from tests/src/com/android/providers/contacts/testutil/DeletedContactUtil.java
rename to test_common/src/com/android/providers/contacts/testutil/DeletedContactUtil.java
diff --git a/tests/src/com/android/providers/contacts/testutil/RawContactUtil.java b/test_common/src/com/android/providers/contacts/testutil/RawContactUtil.java
similarity index 96%
rename from tests/src/com/android/providers/contacts/testutil/RawContactUtil.java
rename to test_common/src/com/android/providers/contacts/testutil/RawContactUtil.java
index f24875b..46b1ff6 100644
--- a/tests/src/com/android/providers/contacts/testutil/RawContactUtil.java
+++ b/test_common/src/com/android/providers/contacts/testutil/RawContactUtil.java
@@ -23,8 +23,6 @@
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
-import android.test.mock.MockContentResolver;
-import com.android.providers.contacts.AccountWithDataSet;
import java.util.List;
@@ -98,11 +96,11 @@
}
public static long createRawContactWithAccountDataSet(ContentResolver resolver,
- AccountWithDataSet accountWithDataSet, String... extras) {
+ String accountName, String accountType, String dataSet, String... extras) {
ContentValues values = new ContentValues();
CommonDatabaseUtils.extrasVarArgsToValues(values, extras);
final Uri uri = TestUtil.maybeAddAccountWithDataSetQueryParameters(
- ContactsContract.RawContacts.CONTENT_URI, accountWithDataSet);
+ ContactsContract.RawContacts.CONTENT_URI, accountName, accountType, dataSet);
Uri contactUri = resolver.insert(uri, values);
return ContentUris.parseId(contactUri);
}
diff --git a/tests/src/com/android/providers/contacts/testutil/TestUtil.java b/test_common/src/com/android/providers/contacts/testutil/TestUtil.java
similarity index 88%
rename from tests/src/com/android/providers/contacts/testutil/TestUtil.java
rename to test_common/src/com/android/providers/contacts/testutil/TestUtil.java
index 05ff61d..6c8c689 100644
--- a/tests/src/com/android/providers/contacts/testutil/TestUtil.java
+++ b/test_common/src/com/android/providers/contacts/testutil/TestUtil.java
@@ -20,7 +20,6 @@
import android.net.Uri;
import android.provider.ContactsContract.RawContacts;
import android.util.Log;
-import com.android.providers.contacts.AccountWithDataSet;
/**
* Common methods used for testing.
@@ -53,14 +52,14 @@
}
public static Uri maybeAddAccountWithDataSetQueryParameters(Uri uri,
- AccountWithDataSet account) {
- if (account == null) {
+ String accountName, String accountType, String dataSet) {
+ if (accountName == null && accountType == null) {
return uri;
}
return uri.buildUpon()
- .appendQueryParameter(RawContacts.ACCOUNT_NAME, account.getAccountName())
- .appendQueryParameter(RawContacts.ACCOUNT_TYPE, account.getAccountType())
- .appendQueryParameter(RawContacts.DATA_SET, account.getDataSet())
+ .appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName)
+ .appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType)
+ .appendQueryParameter(RawContacts.DATA_SET, dataSet)
.build();
}
}
diff --git a/tests/Android.mk b/tests/Android.mk
index f2e0fc1..b3aaa23 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -4,7 +4,11 @@
# We only want this apk build for tests.
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ContactsProviderTestUtils \
+ android-support-test \
+ mockito-target-minus-junit4 \
+ legacy-android-test
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/tests/assets/upgradeTest/pre_upgrade1201.sql b/tests/assets/upgradeTest/pre_upgrade1201.sql
new file mode 100644
index 0000000..6a284ea
--- /dev/null
+++ b/tests/assets/upgradeTest/pre_upgrade1201.sql
@@ -0,0 +1,27 @@
+DELETE FROM accounts;
+DELETE FROM contacts;
+DELETE FROM raw_contacts;
+DELETE FROM data;
+DELETE FROM data_usage_stat;
+
+--CREATE TABLE accounts (_id INTEGER PRIMARY KEY AUTOINCREMENT,account_name TEXT, account_type TEXT, data_set TEXT);
+
+INSERT INTO "accounts" VALUES(1,NULL,NULL,NULL);
+
+--CREATE TABLE contacts (_id INTEGER PRIMARY KEY AUTOINCREMENT,name_raw_contact_id INTEGER REFERENCES raw_contacts(_id),photo_id INTEGER REFERENCES data(_id),photo_file_id INTEGER REFERENCES photo_files(_id),custom_ringtone TEXT,send_to_voicemail INTEGER NOT NULL DEFAULT 0,times_contacted INTEGER NOT NULL DEFAULT 0,last_time_contacted INTEGER,starred INTEGER NOT NULL DEFAULT 0,pinned INTEGER NOT NULL DEFAULT 0,has_phone_number INTEGER NOT NULL DEFAULT 0,lookup TEXT,status_update_id INTEGER REFERENCES data(_id),contact_last_updated_timestamp INTEGER);
+
+INSERT INTO "contacts" VALUES(1,1,NULL,NULL,NULL,0,4,9940760264,0,0,1,NULL,NULL,9940760265);
+INSERT INTO "contacts" VALUES(2,2,NULL,NULL,NULL,0,0,0,0,0,1,NULL,NULL,9940366668);
+
+--CREATE TABLE raw_contacts (_id INTEGER PRIMARY KEY AUTOINCREMENT,account_id INTEGER REFERENCES accounts(_id),sourceid TEXT,backup_id TEXT,raw_contact_is_read_only INTEGER NOT NULL DEFAULT 0,version INTEGER NOT NULL DEFAULT 1,dirty INTEGER NOT NULL DEFAULT 0,deleted INTEGER NOT NULL DEFAULT 0,metadata_dirty INTEGER NOT NULL DEFAULT 0,contact_id INTEGER REFERENCES contacts(_id),aggregation_mode INTEGER NOT NULL DEFAULT 0,aggregation_needed INTEGER NOT NULL DEFAULT 1,custom_ringtone TEXT,send_to_voicemail INTEGER NOT NULL DEFAULT 0,times_contacted INTEGER NOT NULL DEFAULT 0,last_time_contacted INTEGER,starred INTEGER NOT NULL DEFAULT 0,pinned INTEGER NOT NULL DEFAULT 0,display_name TEXT,display_name_alt TEXT,display_name_source INTEGER NOT NULL DEFAULT 0,phonetic_name TEXT,phonetic_name_style TEXT,sort_key TEXT COLLATE PHONEBOOK,phonebook_label TEXT,phonebook_bucket INTEGER,sort_key_alt TEXT COLLATE PHONEBOOK,phonebook_label_alt TEXT,phonebook_bucket_alt INTEGER,name_verified INTEGER NOT NULL DEFAULT 0,sync1 TEXT, sync2 TEXT, sync3 TEXT, sync4 TEXT );
+
+INSERT INTO "raw_contacts" VALUES(1,1,NULL,NULL,0,3,1,0,0,1,0,0,NULL,0,4,9940760264,0,0,'Test','Test',40,NULL,'0','Test','T',20,'Test','T',20,0,NULL,NULL,NULL,NULL);
+INSERT INTO "raw_contacts" VALUES(2,1,NULL,NULL,0,3,1,0,0,2,0,0,NULL,0,0,NULL,0,0,'Test2','Test2',40,NULL,'0','Test2','T',20,'Test2','T',20,0,NULL,NULL,NULL,NULL);
+
+--CREATE TABLE data (_id INTEGER PRIMARY KEY AUTOINCREMENT,package_id INTEGER REFERENCES package(_id),mimetype_id INTEGER REFERENCES mimetype(_id) NOT NULL,raw_contact_id INTEGER REFERENCES raw_contacts(_id) NOT NULL,hash_id TEXT,is_read_only INTEGER NOT NULL DEFAULT 0,is_primary INTEGER NOT NULL DEFAULT 0,is_super_primary INTEGER NOT NULL DEFAULT 0,data_version INTEGER NOT NULL DEFAULT 0,data1 TEXT,data2 TEXT,data3 TEXT,data4 TEXT,data5 TEXT,data6 TEXT,data7 TEXT,data8 TEXT,data9 TEXT,data10 TEXT,data11 TEXT,data12 TEXT,data13 TEXT,data14 TEXT,data15 TEXT,data_sync1 TEXT, data_sync2 TEXT, data_sync3 TEXT, data_sync4 TEXT, carrier_presence INTEGER NOT NULL DEFAULT 0 );
+
+INSERT INTO "data" VALUES(1,NULL,5,1,NULL,0,0,0,0,'555','2',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
+
+--CREATE TABLE data_usage_stat(stat_id INTEGER PRIMARY KEY AUTOINCREMENT, data_id INTEGER NOT NULL, usage_type INTEGER NOT NULL DEFAULT 0, times_used INTEGER NOT NULL DEFAULT 0, last_time_used INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(data_id) REFERENCES data(_id));
+
+INSERT INTO "data_usage_stat" VALUES(1,1,0,4,9940760264);
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 0924154..4d06c4b 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -18,6 +18,7 @@
import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
import static com.android.providers.contacts.TestUtils.cv;
+import static com.android.providers.contacts.TestUtils.dumpCursor;
import android.accounts.Account;
import android.content.ContentProvider;
@@ -144,6 +145,7 @@
@Override
protected void tearDown() throws Exception {
+ mActor.shutdown();
sMockClock.uninstall();
super.tearDown();
}
@@ -1095,6 +1097,9 @@
assertCursorValues(c, values);
} catch (Error e) {
TestUtils.dumpCursor(c);
+
+ // Dump with no selection.
+ TestUtils.dumpUri(mResolver, uri);
throw e;
} finally {
c.close();
@@ -1158,7 +1163,7 @@
}
}
- private void assertCursorValuesOrderly(Cursor cursor, ContentValues... expectedValues) {
+ public static void assertCursorValuesOrderly(Cursor cursor, ContentValues... expectedValues) {
StringBuilder message = new StringBuilder();
cursor.moveToPosition(-1);
for (ContentValues v : expectedValues) {
@@ -1188,7 +1193,7 @@
return true;
}
- private boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues,
+ private static boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues,
StringBuilder msgBuffer) {
for (String column : expectedValues.keySet()) {
int index = cursor.getColumnIndex(column);
@@ -1229,6 +1234,7 @@
final Cursor cursor = mResolver.query(uri, DATA_USAGE_PROJECTION, null, null,
null);
try {
+ dumpCursor(cursor);
assertCursorHasAnyRecordMatch(cursor, cv(Data.DATA1, data1, Data.TIMES_USED, timesUsed,
Data.LAST_TIME_USED, lastTimeUsed));
} finally {
@@ -1372,6 +1378,17 @@
.build();
mResolver.query(uri, null, null, null, null);
}
+
+ protected Uri insertRawContact(ContentValues values) {
+ return TestUtils.insertRawContact(mResolver,
+ getContactsProvider().getDatabaseHelper(), values);
+ }
+
+ protected Uri insertProfileRawContact(ContentValues values) {
+ return TestUtils.insertProfileRawContact(mResolver,
+ getContactsProvider().getProfileProviderForTest().getDatabaseHelper(), values);
+ }
+
/**
* 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/BaseDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java
index 73303d0..0eb12ad 100644
--- a/tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java
@@ -29,7 +29,7 @@
* Run the test like this: <code> runtest -c com.android.providers.contacts.BaseDatabaseHelperUpgradeTest
* contactsprov </code>
*/
-public class BaseDatabaseHelperUpgradeTest extends AndroidTestCase {
+public abstract class BaseDatabaseHelperUpgradeTest extends AndroidTestCase {
protected static final String INTEGER = "INTEGER";
protected static final String TEXT = "TEXT";
@@ -168,7 +168,14 @@
@Override
protected void setUp() throws Exception {
super.setUp();
- mDb = SQLiteDatabase.create(null);
+
+ final String filename = getDatabaseFilename();
+ if (filename == null) {
+ mDb = SQLiteDatabase.create(null);
+ } else {
+ getContext().deleteDatabase(filename);
+ mDb = SQLiteDatabase.openOrCreateDatabase(filename, null);
+ }
}
@Override
@@ -177,6 +184,8 @@
super.tearDown();
}
+ protected abstract String getDatabaseFilename();
+
protected void assertDatabaseStructureSameAsList(TableListEntry[] list, boolean isNewDatabase) {
for (TableListEntry entry : list) {
if (!entry.shouldBeInNewDb) {
diff --git a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
index 8c2a754..3d8b8eb 100644
--- a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
+++ b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
@@ -93,7 +93,6 @@
"' AND " + MetadataSync.DATA_SET + "='" + TEST_DATA_SET2 + "'";
private ContactMetadataProvider mContactMetadataProvider;
- private AccountWithDataSet mTestAccount;
private ContentValues defaultValues;
@Override
@@ -104,7 +103,7 @@
// Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
// are using different dbHelpers.
mContactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
- mActor.provider).getDatabaseHelper(getContext()));
+ mActor.provider).getDatabaseHelper());
setupData();
}
@@ -175,7 +174,7 @@
// Create a raw contact with backupId.
String backupId = "backupId10001";
long rawContactId = RawContactUtil.createRawContactWithAccountDataSet(
- mResolver, mTestAccount);
+ mResolver, TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1);
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
ContentValues values = new ContentValues();
values.put(RawContacts.BACKUP_ID, backupId);
@@ -521,10 +520,8 @@
}
private void setupData() {
- mTestAccount = new AccountWithDataSet(TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1,
- TEST_DATA_SET1);
long rawContactId1 = RawContactUtil.createRawContactWithAccountDataSet(
- mResolver, mTestAccount);
+ mResolver, TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1);
createAccount(TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1);
insertMetadata(getDefaultValues());
insertMetadataSyncState(TEST_ACCOUNT_NAME1, TEST_ACCOUNT_TYPE1, TEST_DATA_SET1,
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index e356107..8aa9b39 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -23,7 +23,6 @@
import android.accounts.AuthenticatorException;
import android.accounts.OnAccountsUpdateListener;
import android.accounts.OperationCanceledException;
-import android.app.admin.DevicePolicyManager;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -45,7 +44,6 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IUserManager;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
@@ -60,10 +58,8 @@
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.StatusUpdates;
import android.test.IsolatedContext;
-import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
-import android.util.Log;
import com.android.providers.contacts.util.ContactsPermissions;
import com.android.providers.contacts.util.MockSharedPreferences;
@@ -103,6 +99,8 @@
private Set<String> mGrantedPermissions = Sets.newHashSet();
private final Set<Uri> mGrantedUriPermissions = Sets.newHashSet();
+ private List<ContentProvider> mAllProviders = new ArrayList<>();
+
private CountryDetector mMockCountryDetector = new CountryDetector(null){
@Override
public Country detectCountry() {
@@ -360,6 +358,11 @@
public void sendBroadcast(Intent intent, String receiverPermission) {
// Ignore.
}
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
};
mMockAccountManager = new MockAccountManager(mProviderContext);
@@ -378,7 +381,11 @@
public <T extends ContentProvider> T addProvider(Class<T> providerClass,
String authority, Context providerContext) throws Exception {
- T provider = providerClass.newInstance();
+ return addProvider(providerClass.newInstance(), authority, providerContext);
+ }
+
+ public <T extends ContentProvider> T addProvider(T provider,
+ String authority, Context providerContext) throws Exception {
ProviderInfo info = new ProviderInfo();
// Here, authority can have "user-id@". We want to use it for addProvider, but provider
@@ -392,6 +399,7 @@
resolver.addProvider(a, provider);
resolver.addProvider("0@" + a, provider);
}
+ mAllProviders.add(provider);
return provider;
}
@@ -734,4 +742,10 @@
static final int COL_CONTACTS_ID = 0;
}
+
+ public void shutdown() {
+ for (ContentProvider provider : mAllProviders) {
+ provider.shutdown();
+ }
+ }
}
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
index ff6a7a2..de0c12a 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
@@ -17,14 +17,21 @@
package com.android.providers.contacts;
import android.content.ContentValues;
+import android.database.ContentObserver;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.ProviderStatus;
import android.provider.ContactsContract.RawContacts;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import com.android.providers.contacts.ContactsDatabaseHelper.LowRes;
import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
@@ -32,16 +39,21 @@
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
@SmallTest
public class ContactsDatabaseHelperTest extends BaseContactsProvider2Test {
+ private static final String TAG = "ContactsDHT";
+
private ContactsDatabaseHelper mDbHelper;
private SQLiteDatabase mDb;
@Override
protected void setUp() throws Exception {
super.setUp();
- mDbHelper = getContactsProvider().getDatabaseHelper(getContext());
+ mDbHelper = getContactsProvider().getDatabaseHelper();
mDb = mDbHelper.getWritableDatabase();
}
@@ -156,13 +168,9 @@
}
/**
- * Test for {@link ContactsDatabaseHelper#getPackageId(String)} and
- * {@link ContactsDatabaseHelper#getMimeTypeId(String)}.
- *
- * We test them at the same time here, to make sure they're not mixing up the caches.
+ * Test for {@link ContactsDatabaseHelper#getPackageId(String)}
*/
- public void testGetPackageId_getMimeTypeId() {
-
+ public void testGetPackageId() {
// Test for getPackageId.
final long packageId1 = mDbHelper.getPackageId("value1");
final long packageId2 = mDbHelper.getPackageId("value2");
@@ -173,44 +181,51 @@
set.add(packageId1);
set.add(packageId2);
set.add(packageId3);
-
assertEquals(3, set.size());
+ // Make sure that repeated calls return the same value
+ assertEquals(packageId1, mDbHelper.getPackageId("value1"));
+ }
+
+ /**
+ * Test for {@link ContactsDatabaseHelper#getMimeTypeId(String)}
+ */
+ public void testGetMimeTypeId() {
// Test for getMimeTypeId.
final long mimetypeId1 = mDbHelper.getMimeTypeId("value1");
final long mimetypeId2 = mDbHelper.getMimeTypeId("value2");
final long mimetypeId3 = mDbHelper.getMimeTypeId("value3");
// Make sure they're all different.
+ final HashSet<Long> set = new HashSet<>();
set.clear();
set.add(mimetypeId1);
set.add(mimetypeId2);
set.add(mimetypeId3);
-
assertEquals(3, set.size());
- // Call with the same values and make sure they return the cached value.
- final long packageId1b = mDbHelper.getPackageId("value1");
- final long mimetypeId1b = mDbHelper.getMimeTypeId("value1");
-
- assertEquals(packageId1, packageId1b);
- assertEquals(mimetypeId1, mimetypeId1b);
-
- // Make sure the caches are also updated.
- assertEquals(packageId2, (long) mDbHelper.mPackageCache.get("value2"));
- assertEquals(mimetypeId2, (long) mDbHelper.mMimetypeCache.get("value2"));
-
- // Clear the cache, but they should still return the values, selecting from the database.
- mDbHelper.mPackageCache.clear();
- mDbHelper.mMimetypeCache.clear();
- assertEquals(packageId1, mDbHelper.getPackageId("value1"));
+ // Make sure repeated calls return the same value
assertEquals(mimetypeId1, mDbHelper.getMimeTypeId("value1"));
+ }
- // Empty the table
- mDb.execSQL("DELETE FROM " + Tables.MIMETYPES);
+ /**
+ * Test for cache {@link ContactsDatabaseHelper#mCommonMimeTypeIdsCache} which stores ids for
+ * common mime types for faster access.
+ */
+ public void testGetCommonMimeTypeIds() {
+ // getMimeTypeId should return the same value as the value stored in the cache
+ for (String commonMimeType : ContactsDatabaseHelper.COMMON_MIME_TYPES) {
+ assertEquals(mDbHelper.mCommonMimeTypeIdsCache.get(commonMimeType).longValue(),
+ mDbHelper.getMimeTypeId(commonMimeType));
+ }
- // We should still have the cached value.
- assertEquals(mimetypeId1, mDbHelper.getMimeTypeId("value1"));
+ // The ids should be available even after deleting them from the table
+ mDb.execSQL("DELETE FROM " + Tables.MIMETYPES + ";");
+
+ for (String commonMimeType : ContactsDatabaseHelper.COMMON_MIME_TYPES) {
+ assertEquals(mDbHelper.mCommonMimeTypeIdsCache.get(commonMimeType).longValue(),
+ mDbHelper.getMimeTypeId(commonMimeType));
+ }
}
/**
@@ -429,4 +444,111 @@
cursor.close();
}
}
+
+ private Integer getIntegerFromExpression(String expression) {
+ try (Cursor c = mDb.rawQuery("SELECT " + expression, null)) {
+ assertTrue(c.moveToPosition(0));
+ if (c.isNull(0)) {
+ return null;
+ }
+ return c.getInt(0);
+ }
+ }
+
+ private Integer checkGetTimesUsedExpression(Integer value) {
+ return getIntegerFromExpression(LowRes.getTimesUsedExpression(
+ value == null ? "NULL" : String.valueOf(value)));
+ }
+
+ public void testGetTimesUsedExpression() {
+ assertEquals((Object) 0, checkGetTimesUsedExpression(null));
+ assertEquals((Object) 0, checkGetTimesUsedExpression(-1));
+ assertEquals((Object) 0, checkGetTimesUsedExpression(-10));
+ assertEquals((Object) 0, checkGetTimesUsedExpression(0));
+ for (int i = 1; i < 10; i++) {
+ assertEquals("value=" + i, (Object) 1, checkGetTimesUsedExpression(i));
+ }
+ for (int i = 10; i < 20; i++) {
+ assertEquals("value=" + i, (Object) 10, checkGetTimesUsedExpression(i));
+ }
+ for (int i = 20; i < 30; i++) {
+ assertEquals("value=" + i, (Object) 20, checkGetTimesUsedExpression(i));
+ }
+
+ assertEquals((Object) 123450, checkGetTimesUsedExpression(123456));
+ }
+
+ private Integer checkGetLastTimeUsedExpression(Integer value) {
+ return getIntegerFromExpression(LowRes.getLastTimeUsedExpression(
+ value == null ? "NULL" : String.valueOf(value)));
+ }
+
+ public void testGetLastTimeUsedExpression() {
+ assertEquals((Object) null, checkGetLastTimeUsedExpression(null));
+ assertEquals((Object) 0, checkGetLastTimeUsedExpression(0));
+ assertEquals((Object) 0, checkGetLastTimeUsedExpression(1));
+ assertEquals((Object) 0, checkGetLastTimeUsedExpression(86399));
+ assertEquals((Object) 86400, checkGetLastTimeUsedExpression(86400));
+
+ for (int i = 1; i < 3; i++) {
+ assertEquals((Object) (86400 * i), checkGetLastTimeUsedExpression(86400 * i));
+ assertEquals((Object) (86400 * i), checkGetLastTimeUsedExpression(86400 * i + 1));
+ assertEquals((Object) (86400 * i), checkGetLastTimeUsedExpression(86400 * i + 86399));
+ }
+ }
+
+ public void testNotifyProviderStatusChange() throws Exception {
+ final AtomicReference<Uri> calledUri = new AtomicReference<>();
+
+ final Handler h = new Handler(Looper.getMainLooper());
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ final ContentObserver observer = new ContentObserver(h) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ calledUri.set(uri);
+ latch.countDown();
+ }
+ };
+
+ // Notify on ProviderStatus.CONTENT_URI.
+ getContext().getContentResolver().registerContentObserver(
+ ProviderStatus.CONTENT_URI,
+ /* notifyForDescendants= */ false, observer);
+
+ // This should trigger it.
+ calledUri.set(null);
+ ContactsDatabaseHelper.notifyProviderStatusChange(getContext());
+
+ assertTrue(latch.await(30, TimeUnit.SECONDS));
+ assertEquals(ProviderStatus.CONTENT_URI, calledUri.get());
+ }
+
+ public void testOpenTimestamp() {
+ final long startTime = System.currentTimeMillis();
+
+ final String dbFilename = "testOpenTimestamp.db";
+
+ getContext().deleteDatabase(dbFilename);
+
+ final ContactsDatabaseHelper dbHelper = ContactsDatabaseHelper.getNewInstanceForTest(
+ mContext, dbFilename);
+
+ dbHelper.getReadableDatabase(); // Open the DB.
+
+ final long creationTime = dbHelper.getDatabaseCreationTime();
+
+ assertTrue("Expected " + creationTime + " >= " + startTime, creationTime >= startTime);
+
+ dbHelper.close();
+
+ // Open again.
+ final ContactsDatabaseHelper dbHelper2 = ContactsDatabaseHelper.getNewInstanceForTest(
+ mContext, dbFilename);
+
+ dbHelper2.getReadableDatabase(); // Open the DB.
+
+ assertEquals(creationTime, dbHelper2.getDatabaseCreationTime());
+ }
}
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
index a607711..185fa03 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
@@ -16,6 +16,11 @@
package com.android.providers.contacts;
+import static com.android.providers.contacts.TestUtils.createDatabaseSnapshot;
+import static com.android.providers.contacts.TestUtils.cv;
+import static com.android.providers.contacts.TestUtils.executeSqlFromAssetFile;
+
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.provider.BaseColumns;
import android.provider.CallLog.Calls;
@@ -40,6 +45,7 @@
import android.test.suitebuilder.annotation.LargeTest;
import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.AggregatedPresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.ContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
@@ -54,9 +60,11 @@
import com.android.providers.contacts.ContactsDatabaseHelper.PackagesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PreAuthorizedUris;
+import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.testutil.TestUtil;
import com.android.providers.contacts.util.PropertyUtils;
import junit.framework.AssertionFailedError;
@@ -81,13 +89,17 @@
private static final String CONTACTS2_DB_1108_ASSET_NAME = "upgradeTest/contacts2_1108.sql";
+ /**
+ * The helper instance. Note we just use it to call the upgrade method. The database
+ * hold by this instance is not used in this test.
+ */
private ContactsDatabaseHelper mHelper;
-
@Override
protected void setUp() throws Exception {
super.setUp();
- mHelper = ContactsDatabaseHelper.getNewInstanceForTest(getContext());
+ mHelper = ContactsDatabaseHelper.getNewInstanceForTest(getContext(),
+ TestUtils.getContactsDatabaseFilename(getContext()));
mHelper.onConfigure(mDb);
}
@@ -97,6 +109,11 @@
super.tearDown();
}
+ @Override
+ protected String getDatabaseFilename() {
+ return TestUtils.getContactsDatabaseFilename(getContext(), "-upgrade-test");
+ }
+
public void testDatabaseCreate() {
mHelper.onCreate(mDb);
assertDatabaseStructureSameAsList(TABLE_LIST, /* isNewDatabase =*/ true);
@@ -104,7 +121,10 @@
public void testDatabaseUpgrade_UpgradeToCurrent() {
create1108(mDb);
- mHelper.onUpgrade(mDb, 1108, mHelper.DATABASE_VERSION);
+ int oldVersion = upgrade(1108, 1200);
+ oldVersion = upgradeTo1201(oldVersion);
+ oldVersion = upgrade(oldVersion, ContactsDatabaseHelper.DATABASE_VERSION);
+
assertDatabaseStructureSameAsList(TABLE_LIST, /* isNewDatabase =*/ false);
}
@@ -113,24 +133,84 @@
*/
public void testDatabaseUpgrade_Incremental() {
create1108(mDb);
- upgradeTo1109();
- upgradeTo1110();
+
+ int oldVersion = 1108;
+ oldVersion = upgradeTo1109(oldVersion);
+ oldVersion = upgrade(oldVersion, ContactsDatabaseHelper.DATABASE_VERSION);
+ assertEquals(ContactsDatabaseHelper.DATABASE_VERSION, oldVersion);
assertDatabaseStructureSameAsList(TABLE_LIST, /* isNewDatabase =*/ false);
}
- private void upgradeTo1109() {
- mHelper.onUpgrade(mDb, 1108, 1109);
+ private int upgradeTo1109(int upgradeFrom) {
+ final int MY_VERSION = 1109;
+ mHelper.onUpgrade(mDb, upgradeFrom, MY_VERSION);
TableStructure calls = new TableStructure(mDb, "calls");
calls.assertHasColumn(Calls.LAST_MODIFIED, INTEGER, false, "0");
TableStructure voicemailStatus = new TableStructure(mDb, "voicemail_status");
voicemailStatus.assertHasColumn(Status.QUOTA_OCCUPIED, INTEGER, false, "-1");
voicemailStatus.assertHasColumn(Status.QUOTA_TOTAL, INTEGER, false, "-1");
+
+ return MY_VERSION;
}
- private void upgradeTo1110() {
- mHelper.onUpgrade(mDb, 1109, 1110);
- // TODO: Test this upgrade.
+ private int upgradeTo1201(int upgradeFrom) {
+ final int MY_VERSION = 1201;
+
+ executeSqlFromAssetFile(getTestContext(), mDb, "upgradeTest/pre_upgrade1201.sql");
+
+ mHelper.onUpgrade(mDb, upgradeFrom, MY_VERSION);
+
+ try (Cursor c = mDb.rawQuery("select * from contacts order by _id", null)) {
+ BaseContactsProvider2Test.assertCursorValuesOrderly(c,
+ cv(Contacts._ID, 1,
+ "last_time_contacted", 0,
+ "x_last_time_contacted", 9940760264L,
+ "times_contacted", 0,
+ "x_times_contacted", 4
+ ),
+ cv(
+ "last_time_contacted", 0,
+ "x_last_time_contacted", 0,
+ "times_contacted", 0,
+ "x_times_contacted", 0
+ ));
+ }
+
+ try (Cursor c = mDb.rawQuery("select * from raw_contacts order by _id", null)) {
+ BaseContactsProvider2Test.assertCursorValuesOrderly(c,
+ cv("_id", 1,
+ "last_time_contacted", 0,
+ "x_last_time_contacted", 9940760264L,
+ "times_contacted", 0,
+ "x_times_contacted", 4
+ ),
+ cv(
+ "last_time_contacted", 0,
+ "x_last_time_contacted", 0,
+ "times_contacted", 0,
+ "x_times_contacted", 0
+ ));
+ }
+
+ try (Cursor c = mDb.rawQuery("select * from data_usage_stat", null)) {
+ BaseContactsProvider2Test.assertCursorValuesOrderly(c,
+ cv(
+ "last_time_used", 0,
+ "x_last_time_used", 9940760264L,
+ "times_used", 0,
+ "x_times_used", 4
+ ));
+ }
+
+ return MY_VERSION;
+ }
+
+ private int upgrade(int upgradeFrom, int upgradeTo) {
+ if (upgradeFrom < upgradeTo) {
+ mHelper.onUpgrade(mDb, upgradeFrom, upgradeTo);
+ }
+ return upgradeTo;
}
/**
@@ -138,15 +218,7 @@
* incrementally from this version.
*/
private void create1108(SQLiteDatabase db) {
- try (InputStream input = getTestContext().getAssets().open(CONTACTS2_DB_1108_ASSET_NAME);) {
- BufferedReader r = new BufferedReader(new InputStreamReader(input));
- String query;
- while ((query = r.readLine()) != null) {
- db.execSQL(query);
- }
- } catch (IOException e) {
- throw new RuntimeException(e.toString());
- }
+ executeSqlFromAssetFile(getTestContext(), db, CONTACTS2_DB_1108_ASSET_NAME);
}
/**
@@ -171,8 +243,10 @@
new TableColumn(Contacts.PHOTO_FILE_ID, INTEGER, false, null),
new TableColumn(Contacts.CUSTOM_RINGTONE, TEXT, false, null),
new TableColumn(Contacts.SEND_TO_VOICEMAIL, INTEGER, true, "0"),
- new TableColumn(Contacts.TIMES_CONTACTED, INTEGER, true, "0"),
- new TableColumn(Contacts.LAST_TIME_CONTACTED, INTEGER, false, null),
+ new TableColumn(Contacts.RAW_TIMES_CONTACTED, INTEGER, true, "0"),
+ new TableColumn(Contacts.RAW_LAST_TIME_CONTACTED, INTEGER, false, null),
+ new TableColumn(Contacts.LR_TIMES_CONTACTED, INTEGER, true, "0"),
+ new TableColumn(Contacts.LR_LAST_TIME_CONTACTED, INTEGER, false, null),
new TableColumn(Contacts.STARRED, INTEGER, true, "0"),
new TableColumn(Contacts.PINNED, INTEGER, true,
String.valueOf(PinnedPositions.UNPINNED)),
@@ -203,8 +277,10 @@
new TableColumn(RawContactsColumns.AGGREGATION_NEEDED, INTEGER, true, "1"),
new TableColumn(RawContacts.CUSTOM_RINGTONE, TEXT, false, null),
new TableColumn(RawContacts.SEND_TO_VOICEMAIL, INTEGER, true, "0"),
- new TableColumn(RawContacts.TIMES_CONTACTED, INTEGER, true, "0"),
- new TableColumn(RawContacts.LAST_TIME_CONTACTED, INTEGER, false, null),
+ new TableColumn(RawContacts.RAW_TIMES_CONTACTED, INTEGER, true, "0"),
+ new TableColumn(RawContacts.RAW_LAST_TIME_CONTACTED, INTEGER, false, null),
+ new TableColumn(RawContacts.LR_TIMES_CONTACTED, INTEGER, true, "0"),
+ new TableColumn(RawContacts.LR_LAST_TIME_CONTACTED, INTEGER, false, null),
new TableColumn(RawContacts.STARRED, INTEGER, true, "0"),
new TableColumn(RawContacts.PINNED, INTEGER, true,
String.valueOf(PinnedPositions.UNPINNED)),
@@ -454,8 +530,10 @@
new TableColumn(DataUsageStatColumns._ID, INTEGER, false, null),
new TableColumn(DataUsageStatColumns.DATA_ID, INTEGER, true, null),
new TableColumn(DataUsageStatColumns.USAGE_TYPE_INT, INTEGER, true, "0"),
- new TableColumn(DataUsageStatColumns.TIMES_USED, INTEGER, true, "0"),
- new TableColumn(DataUsageStatColumns.LAST_TIME_USED, INTEGER, true, "0"),
+ new TableColumn(DataUsageStatColumns.RAW_TIMES_USED, INTEGER, true, "0"),
+ new TableColumn(DataUsageStatColumns.RAW_LAST_TIME_USED, INTEGER, true, "0"),
+ new TableColumn(DataUsageStatColumns.LR_TIMES_USED, INTEGER, true, "0"),
+ new TableColumn(DataUsageStatColumns.LR_LAST_TIME_USED, INTEGER, true, "0"),
};
private static final TableColumn[] METADATA_SYNC_COLUMNS = new TableColumn[] {
@@ -478,6 +556,24 @@
new TableColumn(MetadataSyncState.STATE, BLOB, false, null),
};
+ private static final TableColumn[] PRESENCE_COLUMNS = new TableColumn[] {
+ new TableColumn(StatusUpdates.DATA_ID, INTEGER, false, null),
+ new TableColumn(StatusUpdates.PROTOCOL, INTEGER, true, null),
+ new TableColumn(StatusUpdates.CUSTOM_PROTOCOL, TEXT, false, null),
+ new TableColumn(StatusUpdates.IM_HANDLE, TEXT, false, null),
+ new TableColumn(StatusUpdates.IM_ACCOUNT, TEXT, false, null),
+ new TableColumn(PresenceColumns.CONTACT_ID, INTEGER, false, null),
+ new TableColumn(PresenceColumns.RAW_CONTACT_ID, INTEGER, false, null),
+ new TableColumn(StatusUpdates.PRESENCE, INTEGER, false, null),
+ new TableColumn(StatusUpdates.CHAT_CAPABILITY, INTEGER, true, "0")
+ };
+
+ private static final TableColumn[] AGGREGATED_PRESENCE_COLUMNS = new TableColumn[] {
+ new TableColumn(AggregatedPresenceColumns.CONTACT_ID, INTEGER, false, null),
+ new TableColumn(StatusUpdates.PRESENCE, INTEGER, false, null),
+ new TableColumn(StatusUpdates.CHAT_CAPABILITY, INTEGER, true, "0")
+ };
+
private static final TableListEntry[] TABLE_LIST = {
new TableListEntry(PropertyUtils.Tables.PROPERTIES, PROPERTIES_COLUMNS),
new TableListEntry(Tables.ACCOUNTS, ACCOUNTS_COLUMNS),
@@ -506,6 +602,8 @@
new TableListEntry(Tables.METADATA_SYNC, METADATA_SYNC_COLUMNS),
new TableListEntry(Tables.PRE_AUTHORIZED_URIS, PRE_AUTHORIZED_URIS_COLUMNS),
new TableListEntry(Tables.METADATA_SYNC_STATE, METADATA_SYNC_STATE_COLUMNS),
+ new TableListEntry(Tables.PRESENCE, PRESENCE_COLUMNS),
+ new TableListEntry(Tables.AGGREGATED_PRESENCE, AGGREGATED_PRESENCE_COLUMNS)
};
}
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index f432b0b..930a8fa 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -16,7 +16,9 @@
package com.android.providers.contacts;
+import static com.android.providers.contacts.TestUtils.createDatabaseSnapshot;
import static com.android.providers.contacts.TestUtils.cv;
+import static com.android.providers.contacts.TestUtils.dumpCursor;
import android.accounts.Account;
import android.content.ContentProviderOperation;
@@ -254,8 +256,8 @@
Contacts.CONTACT_STATUS_LABEL,
Contacts.CONTACT_STATUS_ICON,
Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
- DataUsageStatColumns.TIMES_USED,
- DataUsageStatColumns.LAST_TIME_USED,
+ DataUsageStatColumns.LR_TIMES_USED,
+ DataUsageStatColumns.LR_LAST_TIME_USED,
});
}
@@ -299,8 +301,8 @@
Contacts.CONTACT_STATUS_LABEL,
Contacts.CONTACT_STATUS_ICON,
Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
- DataUsageStatColumns.TIMES_USED,
- DataUsageStatColumns.LAST_TIME_USED,
+ DataUsageStatColumns.LR_TIMES_USED,
+ DataUsageStatColumns.LR_LAST_TIME_USED,
Phone.NUMBER,
Phone.TYPE,
Phone.LABEL,
@@ -650,8 +652,8 @@
Contacts.CONTACT_STATUS_ICON,
Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
GroupMembership.GROUP_SOURCE_ID,
- DataUsageStatColumns.TIMES_USED,
- DataUsageStatColumns.LAST_TIME_USED,
+ Contacts.Entity.TIMES_USED,
+ Contacts.Entity.LAST_TIME_USED,
});
}
@@ -711,7 +713,13 @@
PhoneLookup.CONTACT_ID,
PhoneLookup.DATA_ID,
PhoneLookup.LOOKUP_KEY,
+ PhoneLookup.DISPLAY_NAME_SOURCE,
PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.DISPLAY_NAME_ALTERNATIVE,
+ PhoneLookup.PHONETIC_NAME,
+ PhoneLookup.PHONETIC_NAME_STYLE,
+ PhoneLookup.SORT_KEY_PRIMARY,
+ PhoneLookup.SORT_KEY_ALTERNATIVE,
PhoneLookup.LAST_TIME_CONTACTED,
PhoneLookup.TIMES_CONTACTED,
PhoneLookup.STARRED,
@@ -739,7 +747,13 @@
PhoneLookup.CONTACT_ID,
PhoneLookup.DATA_ID,
PhoneLookup.LOOKUP_KEY,
+ PhoneLookup.DISPLAY_NAME_SOURCE,
PhoneLookup.DISPLAY_NAME,
+ PhoneLookup.DISPLAY_NAME_ALTERNATIVE,
+ PhoneLookup.PHONETIC_NAME,
+ PhoneLookup.PHONETIC_NAME_STYLE,
+ PhoneLookup.SORT_KEY_PRIMARY,
+ PhoneLookup.SORT_KEY_ALTERNATIVE,
PhoneLookup.LAST_TIME_CONTACTED,
PhoneLookup.TIMES_CONTACTED,
PhoneLookup.STARRED,
@@ -933,6 +947,13 @@
});
}
+ public void testProviderStatusProjection() {
+ assertProjection(ProviderStatus.CONTENT_URI, new String[]{
+ ProviderStatus.STATUS,
+ ProviderStatus.DATABASE_CREATION_TIMESTAMP,
+ });
+ }
+
public void testRawContactsInsert() {
ContentValues values = new ContentValues();
@@ -946,7 +967,8 @@
values.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
values.put(RawContacts.CUSTOM_RINGTONE, "d");
values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
- values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400 + 123);
+ values.put(RawContacts.TIMES_CONTACTED, 12);
values.put(RawContacts.STARRED, 1);
values.put(RawContacts.SYNC1, "e");
values.put(RawContacts.SYNC2, "f");
@@ -956,6 +978,9 @@
Uri rowUri = mResolver.insert(RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(rowUri);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400);
+ values.put(RawContacts.TIMES_CONTACTED, 0); // This is not insertable.
+
assertStoredValues(rowUri, values);
assertSelection(RawContacts.CONTENT_URI, values, RawContacts._ID, rawContactId);
assertNetworkNotified(true);
@@ -1161,7 +1186,7 @@
Uri dataUri = mResolver.insert(Data.CONTENT_URI, values);
final ContactsProvider2 cp = (ContactsProvider2) getProvider();
- final ContactsDatabaseHelper helper = cp.getDatabaseHelper(mContext);
+ final ContactsDatabaseHelper helper = cp.getDatabaseHelper();
String data1 = values.getAsString(Data.DATA1);
String data2 = values.getAsString(Data.DATA2);
String combineString = data1+data2;
@@ -1222,7 +1247,7 @@
// Check for photo data's hashId is correct or not.
final ContactsProvider2 cp = (ContactsProvider2) getProvider();
- final ContactsDatabaseHelper helper = cp.getDatabaseHelper(mContext);
+ final ContactsDatabaseHelper helper = cp.getDatabaseHelper();
String hashId = helper.getPhotoHashId();
assertStoredValue(dataUri, Data.HASH_ID, hashId);
@@ -1278,11 +1303,11 @@
ContentValues values = new ContentValues();
values.put(RawContacts.CUSTOM_RINGTONE, "d");
values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
- values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400 + 5);
values.put(RawContacts.TIMES_CONTACTED, 54321);
values.put(RawContacts.STARRED, 1);
- Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
+ Uri rawContactUri = insertRawContact(values);
long rawContactId = ContentUris.parseId(rawContactUri);
DataUtil.insertStructuredName(mResolver, rawContactId, "Meghan", "Knox");
@@ -1302,8 +1327,8 @@
values.put(Contacts.DISPLAY_NAME, "Meghan Knox");
values.put(Contacts.CUSTOM_RINGTONE, "d");
values.put(Contacts.SEND_TO_VOICEMAIL, 1);
- values.put(Contacts.LAST_TIME_CONTACTED, 12345);
- values.put(Contacts.TIMES_CONTACTED, 54321);
+ values.put(Contacts.LAST_TIME_CONTACTED, 86400);
+ values.put(Contacts.TIMES_CONTACTED, 54320);
values.put(Contacts.STARRED, 1);
assertStoredValues(ContentUris.withAppendedId(Phone.CONTENT_URI, phoneId), values);
@@ -2055,10 +2080,11 @@
// Note here we use a standalone CP2 so it'll have its own db helper.
// Also use AlteringUserContext here to report the corp user id.
+ final int userId = MockUserManager.CORP_USER.id;
SynchronousContactsProvider2 provider = mActor.addProvider(
- StandaloneContactsProvider2.class,
- "" + MockUserManager.CORP_USER.id + "@com.android.contacts",
- new AlteringUserContext(mActor.getProviderContext(), MockUserManager.CORP_USER.id));
+ new SecondaryUserContactsProvider2(userId),
+ "" + userId + "@com.android.contacts",
+ new AlteringUserContext(mActor.getProviderContext(), userId));
provider.wipeData();
return provider;
}
@@ -2463,11 +2489,11 @@
ContentValues values = new ContentValues();
values.put(RawContacts.CUSTOM_RINGTONE, "d");
values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
- values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400 + 5);
values.put(RawContacts.TIMES_CONTACTED, 54321);
values.put(RawContacts.STARRED, 1);
- Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
+ Uri rawContactUri = insertRawContact(values);
final long rawContactId = ContentUris.parseId(rawContactUri);
DataUtil.insertStructuredName(mResolver, rawContactId, "Meghan", "Knox");
@@ -2486,8 +2512,8 @@
values.put(Contacts.DISPLAY_NAME, "Meghan Knox");
values.put(Contacts.CUSTOM_RINGTONE, "d");
values.put(Contacts.SEND_TO_VOICEMAIL, 1);
- values.put(Contacts.LAST_TIME_CONTACTED, 12345);
- values.put(Contacts.TIMES_CONTACTED, 54321);
+ values.put(Contacts.LAST_TIME_CONTACTED, 86400);
+ values.put(Contacts.TIMES_CONTACTED, 54320);
values.put(Contacts.STARRED, 1);
assertStoredValues(Email.CONTENT_URI, values);
@@ -2842,7 +2868,7 @@
/**
* Tests {@link DataUsageFeedback} correctly bucketize contacts using each
- * {@link DataUsageStatColumns#LAST_TIME_USED}
+ * {@link DataUsageStatColumns#RAW_LAST_TIME_USED}
*/
public void testEmailFilterSortOrderWithOldHistory() {
long rawContactId1 = RawContactUtil.createRawContact(mResolver);
@@ -2997,7 +3023,8 @@
assertStoredValue(dataUri2, Data.IS_SUPER_PRIMARY, 0);
final Uri dataUriWithUsageType = Data.CONTENT_URI.buildUpon().appendQueryParameter(
DataUsageFeedback.USAGE_TYPE, usageTypeString).build();
- assertDataUsageCursorContains(dataUriWithUsageType, emailAddress, timesUsed, lastTimeUsed);
+ assertDataUsageCursorContains(dataUriWithUsageType, emailAddress, 1,
+ 1111111 / 86400 * 86400); // Returned values are lowres.
// Update AggregationException table.
RawContactInfo aggregationContact = new RawContactInfo(
@@ -3034,7 +3061,7 @@
// Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
// are using different dbHelpers.
contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
- mActor.provider).getDatabaseHelper(getContext()));
+ mActor.provider).getDatabaseHelper());
// Create an account first.
String backupId = "backupId001";
String accountType = "accountType";
@@ -3090,7 +3117,7 @@
// Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
// are using different dbHelpers.
contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
- mActor.provider).getDatabaseHelper(getContext()));
+ mActor.provider).getDatabaseHelper());
// Create an account first.
String backupId = "backupId001";
String accountType = "accountType";
@@ -3156,7 +3183,7 @@
// Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
// are using different dbHelpers.
contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
- mActor.provider).getDatabaseHelper(getContext()));
+ mActor.provider).getDatabaseHelper());
// Enable metadataSync flag.
final ContactsProvider2 cp = (ContactsProvider2) getProvider();
cp.setMetadataSyncForTest(true);
@@ -3462,6 +3489,7 @@
StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValues(contactUri, values);
assertSelection(Contacts.CONTENT_URI, values, Contacts._ID, contactId);
}
@@ -3474,6 +3502,9 @@
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
values.put(Contacts.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
+
assertStoredValuesWithProjection(contactUri, values);
assertSelectionWithProjection(Contacts.CONTENT_URI, values, Contacts._ID, contactId);
}
@@ -3496,6 +3527,7 @@
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goulash");
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValuesWithProjection(filterUri1, values);
assertContactFilter(contactId, "goolash");
@@ -3530,6 +3562,7 @@
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goog411@acme.com");
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValuesWithProjection(filterUri1, values);
assertContactFilter(contactId, "goog");
@@ -3556,6 +3589,7 @@
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "18004664411");
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValuesWithProjection(filterUri1, values);
assertContactFilter(contactId, "18004664411");
@@ -3587,7 +3621,7 @@
StatusUpdates.CAPABILITY_HAS_CAMERA);
ContentValues values3 = new ContentValues();
final String phoneNumber3 = "18004664413";
- final int timesContacted3 = 5;
+ final int timesContacted3 = 9;
createContact(values3, "Lotta", "Calling", phoneNumber3,
"c@acme.com", StatusUpdates.AWAY, timesContacted3, 0, 0,
StatusUpdates.CAPABILITY_HAS_VIDEO);
@@ -3603,6 +3637,8 @@
// Send feedback for the 3rd phone number, pretending we called that person via phone.
sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
+ values3.put(Contacts.TIMES_CONTACTED, 10); // Low res.
+
// After the feedback, 3rd contact should be shown after starred one.
assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI,
new ContentValues[] { values4, values3 });
@@ -3612,6 +3648,7 @@
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
// After the feedback, 1st and 3rd contacts should be shown after starred one.
+ values1.put(Contacts.TIMES_CONTACTED, 1); // Low res.
assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI,
new ContentValues[] { values4, values1, values3 });
@@ -3646,6 +3683,10 @@
final ContentValues values6 = new ContentValues(values4);
values6.put(Phone.NUMBER, phoneNumber6);
+ values4.put(Contacts.TIMES_CONTACTED, 1); // Low res.
+ values5.put(Contacts.TIMES_CONTACTED, 1); // Low res.
+ values6.put(Contacts.TIMES_CONTACTED, 1); // Low res.
+
// Phone only strequent should return all phone numbers belonging to the 4th contact,
// and then contact 3.
assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] {values5, values6,
@@ -3654,6 +3695,8 @@
// Send feedback for the 2rd phone number, pretending we send the person a SMS message.
sendFeedback(phoneNumber2, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1);
+ values1.put(Contacts.TIMES_CONTACTED, 1); // Low res.
+
// SMS feedback shouldn't affect phone-only results.
assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] {values5, values6,
values4, values3});
@@ -3736,6 +3779,8 @@
// Contact cid1 again, but it's an email, not a phone call.
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1e);
+ updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1e);
+ updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1e);
// Contacts in this bucket are considered more than 3 days old
sMockClock.setCurrentTimeMillis(fourDaysAgoInMillis);
@@ -3808,12 +3853,15 @@
sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2);
sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2);
+ values1.put(Contacts.TIMES_CONTACTED, 1); // low res.
+ values2.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValues(Contacts.CONTENT_FREQUENT_URI, new ContentValues[] {values2, values1});
- // Three times
- sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
- sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
- sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
+ for (int i = 0; i < 10; i++) {
+ sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
+ }
+
+ values3.put(Contacts.TIMES_CONTACTED, 10); // low res.
assertStoredValues(Contacts.CONTENT_FREQUENT_URI,
new ContentValues[] {values3, values2, values1});
@@ -3874,34 +3922,51 @@
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
- assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 1, 100);
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 1, 0);
- sMockClock.setCurrentTimeMillis(111);
+ sMockClock.setCurrentTimeMillis(86400 + 123);
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
- assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 2, 111);
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 1, 86400);
- sMockClock.setCurrentTimeMillis(123);
- sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1);
+ sMockClock.setCurrentTimeMillis(86400 * 3 + 123);
+ for (int i = 0; i < 11; i++) {
+ sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1);
+ }
- assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 3, 123);
+ // Note here, "a@acme.com" has two data stats rows, 2 and 11. What we get here's the sum
+ // of the lowres values, so # times will be 11, instead of 10 (which is the lowres of the
+ // sum).
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 11, 86400 * 3);
final Uri dataUriWithUsageTypeLongText = Data.CONTENT_URI.buildUpon().appendQueryParameter(
DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_LONG_TEXT).build();
- assertDataUsageCursorContains(dataUriWithUsageTypeLongText, "a@acme.com", 2, 111);
+ assertDataUsageCursorContains(dataUriWithUsageTypeLongText, "a@acme.com", 1, 86400 * 1);
- sMockClock.setCurrentTimeMillis(200);
+ sMockClock.setCurrentTimeMillis(86400 * 4 + 123);
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
- assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 6, 200);
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 12, 86400 * 4);
+
+ sMockClock.setCurrentTimeMillis(86400 * 5 + 123);
+ for (int i = 0; i < 10; i++) {
+ sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
+ }
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 21, 86400 * 5);
+
+ sMockClock.setCurrentTimeMillis(86400 * 6 + 123);
+ for (int i = 0; i < 10; i++) {
+ sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
+ }
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 31, 86400 * 6);
final Uri dataUriWithUsageTypeCall = Data.CONTENT_URI.buildUpon().appendQueryParameter(
DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_CALL).build();
- assertDataUsageCursorContains(dataUriWithUsageTypeCall, "a@acme.com", 3, 200);
+ assertDataUsageCursorContains(dataUriWithUsageTypeCall, "a@acme.com", 20, 86400 * 6);
}
public void testQueryContactGroup() {
@@ -3921,6 +3986,7 @@
Cursor c = mResolver.query(filterUri1, null, null, null, Contacts._ID);
assertEquals(1, c.getCount());
c.moveToFirst();
+ dumpCursor(c);
assertCursorValues(c, values1);
c.close();
@@ -4102,6 +4168,9 @@
long nonProfileRawContactId = createBasicNonProfileContact(nonProfileValues);
long nonProfileContactId = queryContactId(nonProfileRawContactId);
+ nonProfileValues.put(Contacts.TIMES_CONTACTED, 1); // low res.
+ profileValues.put(Contacts.TIMES_CONTACTED, 1); // low res.
+
assertStoredValues(Contacts.CONTENT_URI, nonProfileValues);
assertSelection(Contacts.CONTENT_URI, nonProfileValues, Contacts._ID, nonProfileContactId);
@@ -4115,7 +4184,7 @@
// Create a non-profile contact - this should be returned.
ContentValues nonProfileValues = new ContentValues();
createBasicNonProfileContact(nonProfileValues);
-
+ nonProfileValues.put(Contacts.TIMES_CONTACTED, 1); // low res
assertStoredValues(Contacts.CONTENT_URI, new ContentValues[] {nonProfileValues});
}
@@ -4123,6 +4192,7 @@
ContentValues profileValues = new ContentValues();
createBasicProfileContact(profileValues);
+ profileValues.put(Contacts.TIMES_CONTACTED, 1); // low res
assertStoredValues(Profile.CONTENT_URI, profileValues);
}
@@ -4171,6 +4241,7 @@
// The raw contact view doesn't include the photo ID.
profileValues.remove(Contacts.PHOTO_ID);
+ profileValues.put(Contacts.TIMES_CONTACTED, 1); // low res
assertStoredValues(Profile.CONTENT_RAW_CONTACTS_URI, profileValues);
}
@@ -4180,6 +4251,7 @@
// The raw contact view doesn't include the photo ID.
profileValues.remove(Contacts.PHOTO_ID);
+ profileValues.put(Contacts.TIMES_CONTACTED, 1); // low res
assertStoredValues(ContentUris.withAppendedId(
Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId), profileValues);
}
@@ -5225,18 +5297,29 @@
}
public void testSetSendToVoicemailAndRingtone() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId = RawContactUtil.createRawContactWithName(mResolver);
+ Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+ assertDirty(rawContactUri, true);
+ clearDirty(rawContactUri);
long contactId = queryContactId(rawContactId);
updateSendToVoicemailAndRingtone(contactId, true, "foo");
assertSendToVoicemailAndRingtone(contactId, true, "foo");
- assertNetworkNotified(true);
- assertDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), true);
+ assertNetworkNotified(false);
+ assertMetadataNetworkNotified(true);
+ assertDirty(rawContactUri, false);
+ assertMetadataDirty(rawContactUri, true);
updateSendToVoicemailAndRingtoneWithSelection(contactId, false, "bar");
assertSendToVoicemailAndRingtone(contactId, false, "bar");
- assertNetworkNotified(true);
- assertDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), true);
+ assertNetworkNotified(false);
+ assertMetadataNetworkNotified(true);
+ assertDirty(rawContactUri, false);
+ assertMetadataDirty(rawContactUri, true);
}
public void testSendToVoicemailAndRingtoneAfterAggregation() {
@@ -5294,17 +5377,30 @@
assertSendToVoicemailAndRingtone(queryContactId(rawContactId2), false, "bar");
}
- public void testMarkDirtyAfterAggregation() {
+ public void testMarkMetadataDirtyAfterAggregation() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "i", "j");
long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "k", "l");
+ Uri rawContactUri1 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1);
+ Uri rawContactUri2 = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2);
+ assertDirty(rawContactUri1, true);
+ assertDirty(rawContactUri2, true);
+ clearDirty(rawContactUri1);
+ clearDirty(rawContactUri2);
// Aggregate them
setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
rawContactId1, rawContactId2);
- assertDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId1), true);
- assertDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId2), true);
- assertNetworkNotified(true);
+ assertDirty(rawContactUri1, false);
+ assertDirty(rawContactUri2, false);
+ assertMetadataDirty(rawContactUri1, true);
+ assertMetadataDirty(rawContactUri2, true);
+ assertNetworkNotified(false);
+ assertMetadataNetworkNotified(true);
}
public void testStatusUpdateInsert() {
@@ -6780,7 +6876,7 @@
// Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
// are using different dbHelpers.
contactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
- mActor.provider).getDatabaseHelper(getContext()));
+ mActor.provider).getDatabaseHelper());
// Create a doomed metadata.
String backupId = "backupIdForDoomed";
@@ -6891,28 +6987,41 @@
}
public void testDirtyWhenRawContactInsert() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
+ // When inserting a rawcontact without metadata.
long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
assertDirty(rawContactUri, false);
+ assertMetadataDirty(rawContactUri, false);
assertNetworkNotified(true);
+ assertMetadataNetworkNotified(true);
+ // When inserting a rawcontact with metadata.
ContentValues values = new ContentValues();
values.put(ContactsContract.RawContacts.STARRED, 1);
values.put(ContactsContract.RawContacts.ACCOUNT_NAME, mAccount.name);
values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mAccount.type);
Uri rawContactId2Uri = mResolver.insert(RawContacts.CONTENT_URI, values);
- assertDirty(rawContactId2Uri, true);
+ assertDirty(rawContactId2Uri, false);
+ assertMetadataDirty(rawContactId2Uri, true);
assertNetworkNotified(true);
+ assertMetadataNetworkNotified(true);
}
public void testRawContactDirtyAndVersion() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
final long rawContactId = RawContactUtil.createRawContact(mResolver, mAccount);
Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId);
assertDirty(uri, false);
long version = getVersion(uri);
ContentValues values = new ContentValues();
- values.put(ContactsContract.RawContacts.DIRTY, 0);
values.put(ContactsContract.RawContacts.SEND_TO_VOICEMAIL, 1);
values.put(ContactsContract.RawContacts.AGGREGATION_MODE,
RawContacts.AGGREGATION_MODE_IMMEDIATE);
@@ -6920,9 +7029,10 @@
assertEquals(1, mResolver.update(uri, values, null, null));
assertEquals(version, getVersion(uri));
- // Mark dirty when send_to_voicemail/starred was set.
- assertDirty(uri, true);
- assertNetworkNotified(true);
+ assertDirty(uri, false);
+ assertNetworkNotified(false);
+ assertMetadataDirty(uri, true);
+ assertMetadataNetworkNotified(true);
Uri emailUri = insertEmail(rawContactId, "goo@woo.com");
assertDirty(uri, true);
@@ -8679,13 +8789,22 @@
}
}
- public void testMarkDirtyWhenDataUsageUpdate() {
+ public void testMarkMetadataDirtyWhenDataUsageUpdate() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
final long rid1 = RawContactUtil.createRawContactWithName(mResolver, "contact", "a");
final long did1a = ContentUris.parseId(insertEmail(rid1, "email_1_a@email.com"));
+ final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rid1);
+ assertDirty(rawContactUri, true);
+ clearDirty(rawContactUri);
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1a);
- assertDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rid1), true);
- assertNetworkNotified(true);
+ assertDirty(rawContactUri, false);
+ assertMetadataDirty(rawContactUri, true);
+ assertNetworkNotified(false);
+ assertMetadataNetworkNotified(true);
}
public void testDataUsageFeedbackAndDelete() {
@@ -8734,8 +8853,7 @@
// Now, there's a single frequent. (contact 1)
assertRowCount(1, Contacts.CONTENT_STREQUENT_URI, null, null);
- // time = startTime + 1
- sMockClock.advance();
+ sMockClock.advanceDay();
// Test 2. touch data 1a, 2a and 3a
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1a, did2a, did3a);
@@ -8743,8 +8861,7 @@
// Now, contact 1 and 3 are in frequent.
assertRowCount(2, Contacts.CONTENT_STREQUENT_URI, null, null);
- // time = startTime + 2
- sMockClock.advance();
+ sMockClock.advanceDay();
// Test 2. touch data 2p (call)
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_CALL, did2p);
@@ -8752,71 +8869,70 @@
// There're still two frequent.
assertRowCount(2, Contacts.CONTENT_STREQUENT_URI, null, null);
- // time = startTime + 3
- sMockClock.advance();
+ sMockClock.advanceDay();
// Test 3. touch data 2p and 3p (short text)
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, did2p, did3p);
// Let's check the tables.
-
+// TODO more tests?
// Fist, check the data_usage_stat table, which has no public URI.
assertStoredValuesDb("SELECT " + DataUsageStatColumns.DATA_ID +
"," + DataUsageStatColumns.USAGE_TYPE_INT +
- "," + DataUsageStatColumns.TIMES_USED +
- "," + DataUsageStatColumns.LAST_TIME_USED +
+ "," + DataUsageStatColumns.RAW_TIMES_USED +
+ "," + DataUsageStatColumns.RAW_LAST_TIME_USED +
" FROM " + Tables.DATA_USAGE_STAT, null,
cv(DataUsageStatColumns.DATA_ID, did1a,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_LONG_TEXT,
- DataUsageStatColumns.TIMES_USED, 2,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 1
+ DataUsageStatColumns.RAW_TIMES_USED, 2,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400
),
cv(DataUsageStatColumns.DATA_ID, did2a,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_LONG_TEXT,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 1
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400
),
cv(DataUsageStatColumns.DATA_ID, did3a,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_LONG_TEXT,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 1
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400
),
cv(DataUsageStatColumns.DATA_ID, did2p,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_CALL,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 2
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400 * 2
),
cv(DataUsageStatColumns.DATA_ID, did2p,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_SHORT_TEXT,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 3
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400 * 3
),
cv(DataUsageStatColumns.DATA_ID, did3p,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_SHORT_TEXT,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 3
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400 * 3
)
);
// Next, check the raw_contacts table
assertStoredValuesWithProjection(RawContacts.CONTENT_URI,
cv(RawContacts._ID, rid1,
- RawContacts.TIMES_CONTACTED, 2,
- RawContacts.LAST_TIME_CONTACTED, startTime + 1
+ RawContacts.TIMES_CONTACTED, 1,
+ RawContacts.LAST_TIME_CONTACTED, (startTime + 86400) / 86400 * 86400
),
cv(RawContacts._ID, rid2,
- RawContacts.TIMES_CONTACTED, 3,
- RawContacts.LAST_TIME_CONTACTED, startTime + 3
+ RawContacts.TIMES_CONTACTED, 1,
+ RawContacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(RawContacts._ID, rid3,
- RawContacts.TIMES_CONTACTED, 2,
- RawContacts.LAST_TIME_CONTACTED, startTime + 3
+ RawContacts.TIMES_CONTACTED, 1,
+ RawContacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(RawContacts._ID, rid4,
RawContacts.TIMES_CONTACTED, 0,
@@ -8831,16 +8947,16 @@
// at once.
assertStoredValuesWithProjection(Contacts.CONTENT_URI,
cv(Contacts._ID, cid1,
- Contacts.TIMES_CONTACTED, 4,
- Contacts.LAST_TIME_CONTACTED, startTime + 3
+ Contacts.TIMES_CONTACTED, 1,
+ Contacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(Contacts._ID, cid3,
- Contacts.TIMES_CONTACTED, 2,
- Contacts.LAST_TIME_CONTACTED, startTime + 3
+ Contacts.TIMES_CONTACTED, 1,
+ Contacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(Contacts._ID, cid4,
Contacts.TIMES_CONTACTED, 0,
- Contacts.LAST_TIME_CONTACTED, 0 // For contacts, the default is 0, not null.
+ Contacts.LAST_TIME_CONTACTED, 0
)
);
@@ -8894,15 +9010,24 @@
RawContactUtil.delete(mResolver, ids.mRawContactId, true);
}
- public void testContactUpdate_dirtyForMetadataChange() {
+ public void testContactUpdate_metadataChange() {
+ // Enable metadataSync flag.
+ final ContactsProvider2 cp = (ContactsProvider2) getProvider();
+ cp.setMetadataSyncForTest(true);
+
DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+ Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, ids.mRawContactId);
+ assertDirty(rawContactUri, true);
+ clearDirty(rawContactUri);
ContentValues values = new ContentValues();
values.put(Contacts.PINNED, 1);
ContactUtil.update(mResolver, ids.mContactId, values);
- assertDirty(ContentUris.withAppendedId(RawContacts.CONTENT_URI, ids.mRawContactId), true);
- assertNetworkNotified(true);
+ assertDirty(rawContactUri, false);
+ assertMetadataDirty(rawContactUri, true);
+ assertNetworkNotified(false);
+ assertMetadataNetworkNotified(true);
}
public void testContactUpdate_updatesContactUpdatedTimestamp() {
@@ -9675,10 +9800,13 @@
values.put(RawContacts.CUSTOM_RINGTONE, "beethoven5");
values.put(RawContacts.TIMES_CONTACTED, timesContacted);
- Uri insertionUri = isUserProfile
- ? Profile.CONTENT_RAW_CONTACTS_URI
- : RawContacts.CONTENT_URI;
- Uri rawContactUri = mResolver.insert(insertionUri, values);
+ Uri rawContactUri;
+ if (isUserProfile) {
+ rawContactUri = insertProfileRawContact(values);
+ } else {
+ rawContactUri = insertRawContact(values);
+ }
+
long rawContactId = ContentUris.parseId(rawContactUri);
Uri photoUri = insertPhoto(rawContactId);
long photoId = ContentUris.parseId(photoUri);
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java b/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
index 6a82bf9..03c9e75 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
@@ -60,7 +60,7 @@
*/
public void testTransactionCallback_insert() {
- final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+ final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 86400);
// Insert a raw contact.
mProvider.resetTrasactionCallbackCalledFlags();
@@ -87,14 +87,14 @@
*/
public void testTransactionCallback_update() {
- final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+ final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 86400);
// Make sure to create a raw contact and a profile raw contact.
mResolver.insert(RawContacts.CONTENT_URI, values);
mResolver.insert(Profile.CONTENT_RAW_CONTACTS_URI, values);
values.clear();
- values.put(RawContacts.LAST_TIME_CONTACTED, 99999);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400 * 2);
// Update all raw contacts.
mProvider.resetTrasactionCallbackCalledFlags();
@@ -121,7 +121,7 @@
*/
public void testTransactionCallback_delete() {
- final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+ final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 86400);
// Make sure to create a raw contact and a profile raw contact.
mResolver.insert(RawContacts.CONTENT_URI, values);
@@ -150,7 +150,7 @@
*/
public void testTransactionCallback_bulkInsert() {
- final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+ final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 86400);
// Insert a raw contact.
mProvider.resetTrasactionCallbackCalledFlags();
@@ -179,7 +179,7 @@
ContentProviderOperation.Builder b;
b = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
b.withValue(RawContacts.STARRED, 1);
- b.withValue(RawContacts.TIMES_CONTACTED, 200001);
+ b.withValue(RawContacts.LAST_TIME_CONTACTED, 86400 * 21);
ops.add(b.build());
b = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -197,7 +197,7 @@
private void checkStoredContact() {
assertStoredValues(Contacts.CONTENT_URI, cv(
Contacts.DISPLAY_NAME, "Regular Contact",
- RawContacts.TIMES_CONTACTED, 200001
+ RawContacts.LAST_TIME_CONTACTED, 86400 * 21
));
}
@@ -208,7 +208,7 @@
ContentProviderOperation.Builder b;
b = ContentProviderOperation.newInsert(Profile.CONTENT_RAW_CONTACTS_URI);
b.withValue(RawContacts.STARRED, 1);
- b.withValue(RawContacts.TIMES_CONTACTED, 100001);
+ b.withValue(RawContacts.LAST_TIME_CONTACTED, 86400 * 11);
ops.add(b.build());
b = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -227,7 +227,7 @@
private void checkStoredProfile() {
assertStoredValues(Profile.CONTENT_URI, cv(
Contacts.DISPLAY_NAME, "Profile Contact",
- RawContacts.TIMES_CONTACTED, 100001
+ RawContacts.LAST_TIME_CONTACTED, 86400 * 11
));
}
diff --git a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
index 378c9eb..21d148c 100644
--- a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
+++ b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
@@ -16,6 +16,9 @@
package com.android.providers.contacts;
+import static com.android.providers.contacts.TestUtils.dumpTable;
+import static com.android.providers.contacts.TestUtils.dumpUri;
+
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
@@ -64,12 +67,23 @@
return Contacts.AUTHORITY + ";" + ContactsContract.AUTHORITY;
}
+ private static ContentValues noStats(ContentValues v) {
+ final ContentValues ret = new ContentValues(v);
+ ret.put(People.TIMES_CONTACTED, 0);
+ ret.put(People.LAST_TIME_CONTACTED, 0);
+ return ret;
+ }
+
public void testPeopleInsert() {
ContentValues values = new ContentValues();
putContactValues(values);
Uri uri = mResolver.insert(People.CONTENT_URI, values);
+
+ values = noStats(values);
+
assertStoredValues(uri, values);
+
assertSelection(People.CONTENT_URI, values, "people", People._ID, ContentUris.parseId(uri));
}
@@ -78,6 +92,7 @@
putContactValues(values);
Uri uri = mResolver.insert(People.CONTENT_URI, values);
+ values = noStats(values);
long personId = ContentUris.parseId(uri);
assertStoredValues(uri, values);
assertSelection(People.CONTENT_URI, values, "people", People._ID, personId);
@@ -85,11 +100,13 @@
values.clear();
putContactValues2(values);
mResolver.update(uri, values, null, null);
+ values = noStats(values);
assertStoredValues(uri, values);
values.clear();
putContactValues(values);
mResolver.update(People.CONTENT_URI, values, People._ID + "=" + personId, null);
+ values = noStats(values);
assertStoredValues(uri, values);
}
@@ -207,6 +224,7 @@
values.clear();
putContactValuesExceptName(values);
+ values = noStats(values);
values.put(People.PRIMARY_PHONE_ID, ContentUris.parseId(phoneUri1));
assertStoredValues(phoneUri2, values);
@@ -279,6 +297,7 @@
values.clear();
putContactValuesExceptName(values);
+ values = noStats(values);
values.put(People.PRIMARY_EMAIL_ID, ContentUris.parseId(emailUri1));
assertStoredValues(emailUri2, values);
@@ -314,9 +333,9 @@
int timesContactedAfter =
Integer.parseInt(getStoredValue(personUri, People.TIMES_CONTACTED));
- assertTrue(lastContacted >= timeBefore);
- assertTrue(lastContacted <= timeAfter);
- assertEquals(timesContactedAfter, timesContactedBefore + 1);
+ // No longer supported as of O.
+ assertEquals(0, lastContacted);
+ assertEquals(0, timesContactedAfter);
}
public void testOrganizationsInsert() {
@@ -401,6 +420,7 @@
// The result is joined with People
putContactValues(expectedResults[0]);
+ expectedResults[0] = noStats(expectedResults[0]);
assertStoredValues(uri, expectedResults);
assertSelection(Phones.CONTENT_URI, values, "phones",
Phones._ID, ContentUris.parseId(uri));
@@ -412,6 +432,7 @@
// Now the person should be joined with Phone
values.clear();
putContactValues(values);
+ values = noStats(values);
values.put(People.TYPE, Phones.TYPE_CUSTOM);
values.put(People.LABEL, "Directory");
values.put(People.NUMBER, "1-800-4664-411");
@@ -541,6 +562,9 @@
// The result is joined with People
putContactValues(values);
+
+ values = noStats(values);
+
assertStoredValues(uri, values);
assertSelection(ContactMethods.CONTENT_URI, values, "contact_methods",
ContactMethods._ID, ContentUris.parseId(uri));
diff --git a/tests/src/com/android/providers/contacts/PhotoStoreTest.java b/tests/src/com/android/providers/contacts/PhotoStoreTest.java
index 4e797f7..8ba0438 100644
--- a/tests/src/com/android/providers/contacts/PhotoStoreTest.java
+++ b/tests/src/com/android/providers/contacts/PhotoStoreTest.java
@@ -56,7 +56,7 @@
mProvider = ((SynchronousContactsProvider2) mActor.provider);
mPhotoStore = mProvider.getPhotoStore();
mProvider.wipeData();
- mDb = mProvider.getDatabaseHelper(getContext()).getReadableDatabase();
+ mDb = mProvider.getDatabaseHelper().getReadableDatabase();
}
@Override
diff --git a/tests/src/com/android/providers/contacts/RenamingDelegatingContext.java b/tests/src/com/android/providers/contacts/RenamingDelegatingContext.java
new file mode 100644
index 0000000..260b730
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/RenamingDelegatingContext.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2007 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.ContentProvider;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.database.DatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.FileUtils;
+import android.util.Log;
+
+import com.google.android.collect.Sets;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.util.Set;
+
+/**
+ * This file was copied from framework/base. The DB related file names now understand fullpath
+ * filenames and will not append the prefix for them.
+ */
+public class RenamingDelegatingContext extends ContextWrapper {
+
+ private Context mFileContext;
+ private String mFilePrefix = null;
+ private File mCacheDir;
+ private final Object mSync = new Object();
+
+ private Set<String> mDatabaseNames = Sets.newHashSet();
+ private Set<String> mFileNames = Sets.newHashSet();
+
+ public static <T extends ContentProvider> T providerWithRenamedContext(
+ Class<T> contentProvider, Context c, String filePrefix)
+ throws IllegalAccessException, InstantiationException {
+ return providerWithRenamedContext(contentProvider, c, filePrefix, false);
+ }
+
+ public static <T extends ContentProvider> T providerWithRenamedContext(
+ Class<T> contentProvider, Context c, String filePrefix,
+ boolean allowAccessToExistingFilesAndDbs)
+ throws IllegalAccessException, InstantiationException {
+ Class<T> mProviderClass = contentProvider;
+ T mProvider = mProviderClass.newInstance();
+ RenamingDelegatingContext mContext = new RenamingDelegatingContext(c, filePrefix);
+ if (allowAccessToExistingFilesAndDbs) {
+ mContext.makeExistingFilesAndDbsAccessible();
+ }
+ mProvider.attachInfoForTesting(mContext, null);
+ return mProvider;
+ }
+
+ /**
+ * Makes accessible all files and databases whose names match the filePrefix that was passed to
+ * the constructor. Normally only files and databases that were created through this context are
+ * accessible.
+ */
+ public void makeExistingFilesAndDbsAccessible() {
+ String[] databaseList = mFileContext.databaseList();
+ for (String diskName : databaseList) {
+ if (shouldDiskNameBeVisible(diskName)) {
+ mDatabaseNames.add(publicNameFromDiskName(diskName));
+ }
+ }
+ String[] fileList = mFileContext.fileList();
+ for (String diskName : fileList) {
+ if (shouldDiskNameBeVisible(diskName)) {
+ mFileNames.add(publicNameFromDiskName(diskName));
+ }
+ }
+ }
+
+ /**
+ * Returns if the given diskName starts with the given prefix or not.
+ * @param diskName name of the database/file.
+ */
+ boolean shouldDiskNameBeVisible(String diskName) {
+ return diskName.startsWith(mFilePrefix);
+ }
+
+ /**
+ * Returns the public name (everything following the prefix) of the given diskName.
+ * @param diskName name of the database/file.
+ */
+ String publicNameFromDiskName(String diskName) {
+ if (!shouldDiskNameBeVisible(diskName)) {
+ throw new IllegalArgumentException("disk file should not be visible: " + diskName);
+ }
+ return diskName.substring(mFilePrefix.length(), diskName.length());
+ }
+
+ /**
+ * @param context : the context that will be delegated.
+ * @param filePrefix : a prefix with which database and file names will be
+ * prefixed.
+ */
+ public RenamingDelegatingContext(Context context, String filePrefix) {
+ super(context);
+ mFileContext = context;
+ mFilePrefix = filePrefix;
+ }
+
+ /**
+ * @param context : the context that will be delegated.
+ * @param fileContext : the context that file and db methods will be delegated to
+ * @param filePrefix : a prefix with which database and file names will be
+ * prefixed.
+ */
+ public RenamingDelegatingContext(Context context, Context fileContext, String filePrefix) {
+ super(context);
+ mFileContext = fileContext;
+ mFilePrefix = filePrefix;
+ }
+
+ public String getDatabasePrefix() {
+ return mFilePrefix;
+ }
+
+ private String renamedFileName(String name) {
+ return mFilePrefix + name;
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String name,
+ int mode, SQLiteDatabase.CursorFactory factory) {
+ if (name.startsWith("/")) {
+ return mFileContext.openOrCreateDatabase(name, mode, factory);
+ }
+ final String internalName = renamedFileName(name);
+ if (!mDatabaseNames.contains(name)) {
+ mDatabaseNames.add(name);
+ mFileContext.deleteDatabase(internalName);
+ }
+ return mFileContext.openOrCreateDatabase(internalName, mode, factory);
+ }
+
+ @Override
+ public SQLiteDatabase openOrCreateDatabase(String name,
+ int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
+ if (name.startsWith("/")) {
+ return mFileContext.openOrCreateDatabase(name, mode, factory, errorHandler);
+ }
+ final String internalName = renamedFileName(name);
+ if (!mDatabaseNames.contains(name)) {
+ mDatabaseNames.add(name);
+ mFileContext.deleteDatabase(internalName);
+ }
+ return mFileContext.openOrCreateDatabase(internalName, mode, factory, errorHandler);
+ }
+
+ @Override
+ public boolean deleteDatabase(String name) {
+ if (name.startsWith("/")) {
+ return mFileContext.deleteDatabase(name);
+ }
+ if (mDatabaseNames.contains(name)) {
+ mDatabaseNames.remove(name);
+ return mFileContext.deleteDatabase(renamedFileName(name));
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public File getDatabasePath(String name) {
+ if (name.startsWith("/")) {
+ return mFileContext.getDatabasePath(name);
+ }
+ return mFileContext.getDatabasePath(renamedFileName(name));
+ }
+
+ @Override
+ public String[] databaseList() {
+ return mDatabaseNames.toArray(new String[]{});
+ }
+
+ @Override
+ public FileInputStream openFileInput(String name)
+ throws FileNotFoundException {
+ final String internalName = renamedFileName(name);
+ if (mFileNames.contains(name)) {
+ return mFileContext.openFileInput(internalName);
+ } else {
+ throw new FileNotFoundException(internalName);
+ }
+ }
+
+ @Override
+ public FileOutputStream openFileOutput(String name, int mode)
+ throws FileNotFoundException {
+ mFileNames.add(name);
+ return mFileContext.openFileOutput(renamedFileName(name), mode);
+ }
+
+ @Override
+ public File getFileStreamPath(String name) {
+ return mFileContext.getFileStreamPath(renamedFileName(name));
+ }
+
+ @Override
+ public boolean deleteFile(String name) {
+ if (mFileNames.contains(name)) {
+ mFileNames.remove(name);
+ return mFileContext.deleteFile(renamedFileName(name));
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String[] fileList() {
+ return mFileNames.toArray(new String[]{});
+ }
+
+ /**
+ * In order to support calls to getCacheDir(), we create a temp cache dir (inside the real
+ * one) and return it instead. This code is basically getCacheDir(), except it uses the real
+ * cache dir as the parent directory and creates a test cache dir inside that.
+ */
+ @Override
+ public File getCacheDir() {
+ synchronized (mSync) {
+ if (mCacheDir == null) {
+ mCacheDir = new File(mFileContext.getCacheDir(), renamedFileName("cache"));
+ }
+ if (!mCacheDir.exists()) {
+ if(!mCacheDir.mkdirs()) {
+ Log.w("RenamingDelegatingContext", "Unable to create cache directory");
+ return null;
+ }
+ FileUtils.setPermissions(
+ mCacheDir.getPath(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+ -1, -1);
+ }
+ }
+ return mCacheDir;
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/SecondaryUserContactsProvider2.java b/tests/src/com/android/providers/contacts/SecondaryUserContactsProvider2.java
new file mode 100644
index 0000000..61ae1ca
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/SecondaryUserContactsProvider2.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 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;
+
+/**
+ * A subclass of {@link SynchronousContactsProvider2} that uses a different DB for secondary users.
+ */
+public class SecondaryUserContactsProvider2 extends SynchronousContactsProvider2 {
+ private final String mDbSuffix;
+ private ContactsDatabaseHelper mDbHelper;
+
+ public SecondaryUserContactsProvider2(int userId) {
+ mDbSuffix = "-u" + userId;
+ }
+
+ @Override
+ public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
+ if (mDbHelper == null) {
+ mDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context,
+ TestUtils.getContactsDatabaseFilename(context, mDbSuffix));
+ }
+ return mDbHelper;
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java b/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
index e7b80a0..32caa73 100644
--- a/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
+++ b/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
@@ -17,6 +17,7 @@
package com.android.providers.contacts;
import static com.android.providers.contacts.EvenMoreAsserts.assertThrows;
+import static com.android.providers.contacts.TestUtils.cv;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
@@ -25,6 +26,7 @@
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.providers.contacts.testutil.RawContactUtil;
@@ -42,36 +44,50 @@
public class SqlInjectionDetectionTest extends BaseContactsProvider2Test {
private static final String[] PHONE_ID_PROJECTION = new String[] { Phone._ID };
- public void testPhoneQueryValid() {
- long rawContactId = RawContactUtil.createRawContactWithName(mResolver, "Hot", "Tamale");
- insertPhoneNumber(rawContactId, "555-123-4567");
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+ public void testQueryValid() {
assertQueryValid(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
Phone.NUMBER + "='555-123-4567'", null);
+
+ // The following tables are whitelisted.
+ assertQueryValid(Data.CONTENT_URI, null,
+ "data._id in default_directory", null);
}
public void testPhoneQueryBadProjection() {
- long rawContactId = RawContactUtil.createRawContactWithName(mResolver, "Hot", "Tamale");
- insertPhoneNumber(rawContactId, "555-123-4567");
-
- assertQueryThrows(IllegalArgumentException.class, Phone.CONTENT_URI,
+ assertQueryThrows(Phone.CONTENT_URI,
new String[] { "0 UNION SELECT _id FROM view_data--" }, null, null);
+
+ // Invalid column names should be detected too.
+ assertQueryThrows(Phone.CONTENT_URI, new String[] { "a" }, null, null);
+ assertQueryThrows(Phone.CONTENT_URI, new String[] { " _id" }, null, null);
+
+ // This is still invalid because we only allow exact column names in projections.
+ assertQueryThrows(Phone.CONTENT_URI, new String[] { "[_id]" }, null, null);
}
public void testPhoneQueryBadSelection() {
- long rawContactId = RawContactUtil.createRawContactWithName(mResolver, "Hot", "Tamale");
- insertPhoneNumber(rawContactId, "555-123-4567");
-
- assertQueryThrows(SQLiteException.class, Phone.CONTENT_URI, PHONE_ID_PROJECTION,
+ assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
"0=1) UNION SELECT _id FROM view_data--", null);
+ assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION, ";delete from contacts", null);
+ assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
+ "_id in data_usage_stat", null);
+ assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
+ "_id in (select _id from default_directory)", null);
}
public void testPhoneQueryBadSortOrder() {
- long rawContactId = RawContactUtil.createRawContactWithName(mResolver, "Hot", "Tamale");
- insertPhoneNumber(rawContactId, "555-123-4567");
-
- assertQueryThrows(SQLiteException.class, Phone.CONTENT_URI,
+ assertQueryThrows(Phone.CONTENT_URI,
PHONE_ID_PROJECTION, null, "_id UNION SELECT _id FROM view_data--");
+ assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION, null, ";delete from contacts");
+ assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION, null,
+ "_id in data_usage_stat");
+ assertQueryThrows(Phone.CONTENT_URI, PHONE_ID_PROJECTION,
+ null, "exists (select _id from default_directory)");
}
public void testPhoneQueryBadLimit() {
@@ -100,14 +116,39 @@
c.close();
}
- private <T extends Exception> void assertQueryThrows(Class<T> exception, final Uri uri,
+ private <T extends Exception> void assertQueryThrows(final Uri uri,
final String[] projection, final String selection, final String sortOrder) {
- assertThrows(exception, new Runnable() {
- @Override
- public void run() {
+ assertThrows(IllegalArgumentException.class, () -> {
final Cursor c = mResolver.query(uri, projection, selection, null, sortOrder);
c.close();
- }
+ });
+ }
+
+ public void testBadDelete() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ mResolver.delete(Contacts.CONTENT_URI, ";delete from contacts;--", null);
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ mResolver.delete(Contacts.CONTENT_URI, "_id in data_usage_stat", null);
+ });
+ }
+
+ public void testBadUpdate() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ mResolver.update(Data.CONTENT_URI, cv(), ";delete from contacts;--", null);
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ mResolver.update(Data.CONTENT_URI, cv(), "_id in data_usage_stat", null);
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ mResolver.update(Data.CONTENT_URI, cv("_id/**/", 1), null, null);
+ });
+ mResolver.update(Data.CONTENT_URI, cv("[data1]", 1), null, null); // this is actually fine
+ }
+
+ public void testBadInsert() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ mResolver.insert(Data.CONTENT_URI, cv("_id/**/", 1));
});
}
}
diff --git a/tests/src/com/android/providers/contacts/StandaloneContactsProvider2.java b/tests/src/com/android/providers/contacts/StandaloneContactsProvider2.java
deleted file mode 100644
index 8dd09bf..0000000
--- a/tests/src/com/android/providers/contacts/StandaloneContactsProvider2.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-/**
- * A subclass of {@link SynchronousContactsProvider2} that doesn't reuse the database helper.
- */
-public class StandaloneContactsProvider2 extends SynchronousContactsProvider2 {
- private static ContactsDatabaseHelper mDbHelper;
-
- public StandaloneContactsProvider2() {
- // No need to wipe data for this instance since it doesn't reuse the db helper.
- setDataWipeEnabled(false);
- }
-
- @Override
- public ContactsDatabaseHelper getDatabaseHelper(final Context context) {
- if (mDbHelper == null) {
- mDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context);
- }
- return mDbHelper;
- }
-
- @Override
- public void setDataWipeEnabled(boolean flag) {
- // No need to wipe data for this instance since it doesn't reuse the db helper.
- super.setDataWipeEnabled(false);
- }
-}
diff --git a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
index 19878f8..6ed8711 100644
--- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
@@ -34,7 +34,6 @@
private static Boolean sDataWiped = false;
private static ContactsDatabaseHelper sDbHelper;
- private boolean mDataWipeEnabled = true;
private Account mAccount;
private boolean mNetworkNotified;
private boolean mMetadataNetworkNotified;
@@ -42,9 +41,10 @@
private boolean mIsVoiceCapable = true;
@Override
- public ContactsDatabaseHelper getDatabaseHelper(final Context context) {
+ public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
if (sDbHelper == null) {
- sDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context);
+ sDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context,
+ TestUtils.getContactsDatabaseFilename(getContext()));
}
return sDbHelper;
}
@@ -54,8 +54,8 @@
return new SynchronousProfileProvider(this);
}
- public void setDataWipeEnabled(boolean flag) {
- mDataWipeEnabled = flag;
+ public ProfileDatabaseHelper getProfileDatabaseHelper() {
+ return getProfileProviderForTest().getDatabaseHelper();
}
@Override
@@ -100,12 +100,10 @@
@Override
public boolean onCreate() {
boolean created = super.onCreate();
- if (mDataWipeEnabled) {
- synchronized (sDataWiped) {
- if (!sDataWiped) {
- sDataWiped = true;
- wipeData();
- }
+ synchronized (sDataWiped) {
+ if (!sDataWiped) {
+ sDataWiped = true;
+ wipeData();
}
}
return created;
@@ -192,7 +190,7 @@
}
public void prepareForFullAggregation(int maxContact) {
- SQLiteDatabase db = getDatabaseHelper(getContext()).getWritableDatabase();
+ SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
db.execSQL("UPDATE raw_contacts SET aggregation_mode=0,aggregation_needed=1;");
long rowId =
db.compileStatement("SELECT _id FROM raw_contacts LIMIT 1 OFFSET " + maxContact)
@@ -201,12 +199,12 @@
}
public long getRawContactCount() {
- SQLiteDatabase db = getDatabaseHelper(getContext()).getReadableDatabase();
+ SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
return db.compileStatement("SELECT COUNT(*) FROM raw_contacts").simpleQueryForLong();
}
public long getContactCount() {
- SQLiteDatabase db = getDatabaseHelper(getContext()).getReadableDatabase();
+ SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
return db.compileStatement("SELECT COUNT(*) FROM contacts").simpleQueryForLong();
}
@@ -214,7 +212,7 @@
public void wipeData() {
Log.i(TAG, "wipeData");
super.wipeData();
- SQLiteDatabase db = getDatabaseHelper(getContext()).getWritableDatabase();
+ SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
db.execSQL("replace into SQLITE_SEQUENCE (name,seq) values('raw_contacts', 42)");
db.execSQL("replace into SQLITE_SEQUENCE (name,seq) values('contacts', 2009)");
db.execSQL("replace into SQLITE_SEQUENCE (name,seq) values('data', 777)");
diff --git a/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java b/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
index 308e67a..79356a5 100644
--- a/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
+++ b/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
@@ -32,9 +32,10 @@
}
@Override
- protected ProfileDatabaseHelper getDatabaseHelper(final Context context) {
+ protected ProfileDatabaseHelper newDatabaseHelper(final Context context) {
if (sDbHelper == null) {
- sDbHelper = ProfileDatabaseHelper.getNewInstanceForTest(context);
+ sDbHelper = ProfileDatabaseHelper.getNewInstanceForTest(context,
+ TestUtils.getProfileDatabaseFilename(getContext()));
}
return sDbHelper;
}
diff --git a/tests/src/com/android/providers/contacts/TestUtils.java b/tests/src/com/android/providers/contacts/TestUtils.java
index b6d6a27..322e5b4 100644
--- a/tests/src/com/android/providers/contacts/TestUtils.java
+++ b/tests/src/com/android/providers/contacts/TestUtils.java
@@ -16,21 +16,118 @@
package com.android.providers.contacts;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.os.FileUtils;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Profile;
+import android.provider.ContactsContract.RawContacts;
+import android.support.annotation.Nullable;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
import android.util.Log;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+
import junit.framework.Assert;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
public class TestUtils {
+ private static final String TAG = "ContactsTestUtils";
+
private TestUtils() {
}
+ /**
+ * We normally use in-memory DBs in unit tests, because that's faster, but it's impossible to
+ * look at intermediate databases when something is failing. When this flag is set to true,
+ * we'll switch to file-based DBs, so we can call {@link #createDatabaseSnapshot}
+ * , pull the snapshot DBs and take a look at them.
+ */
+ public static final boolean ENABLE_DATABASE_SNAPSHOT = false; // DO NOT SUBMIT WITH TRUE.
+
+ private static final Object sDatabasePathLock = new Object();
+ private static File sDatabasePath = null;
+
+ private static String getDatabaseFile(Context context, @Nullable String name) {
+ if (!ENABLE_DATABASE_SNAPSHOT) {
+ return null; // Use the in-memory DB.
+ }
+ synchronized (sDatabasePathLock) {
+ if (sDatabasePath == null) {
+ final File path = new File(context.getCacheDir(), "test-db");
+ if (path.exists()) {
+ Assert.assertTrue("Unable to delete directory: " + path,
+ FileUtils.deleteContents(path));
+ } else {
+ Assert.assertTrue("Unable to create directory: " + path, path.mkdirs());
+ }
+ Log.i(TAG, "Test DB directory: " + path);
+
+ sDatabasePath = path;
+ }
+ final File ret;
+ if (name == null) {
+ ret = sDatabasePath;
+ } else {
+ ret = new File(sDatabasePath, name);
+ Log.i(TAG, "Test DB file: " + ret);
+ }
+ return ret.getAbsolutePath();
+ }
+ }
+
+ public static String getContactsDatabaseFilename(Context context) {
+ return getContactsDatabaseFilename(context, "");
+ }
+
+ public static String getContactsDatabaseFilename(Context context, String suffix) {
+ return getDatabaseFile(context, "contacts2" + suffix + ".db");
+ }
+
+ public static String getProfileDatabaseFilename(Context context) {
+ return getProfileDatabaseFilename(context, "");
+ }
+
+ public static String getProfileDatabaseFilename(Context context, String suffix) {
+ return getDatabaseFile(context, "profile.db" + suffix + ".db");
+ }
+
+ public static void createDatabaseSnapshot(Context context, String name) {
+ Assert.assertTrue(
+ "ENABLE_DATABASE_SNAPSHOT must be set to true to create database snapshot",
+ ENABLE_DATABASE_SNAPSHOT);
+
+ final File fromDir = new File(getDatabaseFile(context, null));
+ final File toDir = new File(context.getCacheDir(), "snapshot-" + name);
+ if (toDir.exists()) {
+ Assert.assertTrue("Unable to delete directory: " + toDir,
+ FileUtils.deleteContents(toDir));
+ } else {
+ Assert.assertTrue("Unable to create directory: " + toDir, toDir.mkdirs());
+ }
+
+ Log.w(TAG, "Copying database files from '" + fromDir + "' into '" + toDir + "'...");
+
+ for (File file : fromDir.listFiles()) {
+ try {
+ final File to = new File(toDir, file.getName());
+ FileUtils.copyFileOrThrow(file, to);
+ Log.i(TAG, "Created: " + to);
+ } catch (IOException e) {
+ Assert.fail("Failed to copy file: " + e.toString());
+ }
+ }
+ }
+
/** Convenient method to create a ContentValues */
public static ContentValues cv(Object... namesAndValues) {
Assert.assertTrue((namesAndValues.length % 2) == 0);
@@ -58,20 +155,20 @@
* Writes the content of a cursor to the log.
*/
public static final void dumpCursor(Cursor c) {
- final String TAG = "contacts";
-
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < c.getColumnCount(); i++) {
- if (sb.length() > 0) sb.append("|");
+ if (i > 0) sb.append("|");
sb.append(c.getColumnName(i));
}
Log.i(TAG, sb.toString());
+ final int pos = c.getPosition();
+
c.moveToPosition(-1);
while (c.moveToNext()) {
sb.setLength(0);
for (int i = 0; i < c.getColumnCount(); i++) {
- if (sb.length() > 0) sb.append("|");
+ if (i > 0) sb.append("|");
if (c.getType(i) == Cursor.FIELD_TYPE_BLOB) {
byte[] blob = c.getBlob(i);
@@ -84,6 +181,29 @@
}
Log.i(TAG, sb.toString());
}
+
+ c.moveToPosition(pos);
+ }
+
+ public static void dumpTable(SQLiteDatabase db, String name) {
+ Log.i(TAG, "Dumping table: " + name);
+ try (Cursor c = db.rawQuery(String.format("SELECT * FROM %s", name), null)) {
+ dumpCursor(c);
+ }
+ }
+
+ public static void dumpUri(Context context, Uri uri) {
+ Log.i(TAG, "Dumping URI: " + uri);
+ try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) {
+ dumpCursor(c);
+ }
+ }
+
+ public static void dumpUri(ContentResolver resolver, Uri uri) {
+ Log.i(TAG, "Dumping URI: " + uri);
+ try (Cursor c = resolver.query(uri, null, null, null, null)) {
+ dumpCursor(c);
+ }
}
/**
@@ -101,4 +221,57 @@
return "[Failed to write to file: " + e.getMessage() + "]";
}
}
+
+ public static Uri insertRawContact(
+ ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values) {
+ return insertRawContact(RawContacts.CONTENT_URI, resolver, dbh, values);
+ }
+
+ public static Uri insertProfileRawContact(
+ ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values) {
+ return insertRawContact(Profile.CONTENT_RAW_CONTACTS_URI, resolver, dbh, values);
+ }
+
+ private static Uri insertRawContact(Uri tableUri,
+ ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values) {
+ final SQLiteDatabase db = dbh.getWritableDatabase();
+
+ final Uri rowUri = resolver.insert(tableUri, values);
+ Long timesContacted = values.getAsLong(RawContacts.LR_TIMES_CONTACTED);
+ if (timesContacted != null) {
+ // TIMES_CONTACTED is no longer modifiable via resolver, so we update the DB directly.
+ final long rid = Long.parseLong(rowUri.getLastPathSegment());
+
+ final String[] args = {String.valueOf(rid)};
+
+ db.update(Tables.RAW_CONTACTS,
+ cv(RawContacts.RAW_TIMES_CONTACTED, (long) timesContacted),
+ "_id=?", args);
+
+ // Then propagate it to contacts too.
+ db.execSQL("UPDATE " + Tables.CONTACTS
+ + " SET " + Contacts.RAW_TIMES_CONTACTED + " = ("
+ + " SELECT sum(" + RawContacts.RAW_TIMES_CONTACTED + ") FROM "
+ + Tables.RAW_CONTACTS + " AS r "
+ + " WHERE " + Tables.CONTACTS + "._id = r." + RawContacts.CONTACT_ID
+ + " GROUP BY r." + RawContacts.CONTACT_ID + ")");
+ }
+ return rowUri;
+ }
+
+ public static void executeSqlFromAssetFile(
+ Context context, SQLiteDatabase db, String assetName) {
+ try (InputStream input = context.getAssets().open(assetName);) {
+ BufferedReader r = new BufferedReader(new InputStreamReader(input));
+ String query;
+ while ((query = r.readLine()) != null) {
+ if (query.trim().length() == 0 || query.startsWith("--")) {
+ continue;
+ }
+ db.execSQL(query);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
}
diff --git a/tests/src/com/android/providers/contacts/sqlite/DatabaseAnalyzerTest.java b/tests/src/com/android/providers/contacts/sqlite/DatabaseAnalyzerTest.java
new file mode 100644
index 0000000..568e1e8
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/sqlite/DatabaseAnalyzerTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts.sqlite;
+
+import android.test.AndroidTestCase;
+
+import com.android.providers.contacts.ContactsDatabaseHelper;
+import com.android.providers.contacts.TestUtils;
+
+import java.util.List;
+
+public class DatabaseAnalyzerTest extends AndroidTestCase {
+ public void testFindTableViewsAllowingColumns() {
+ final ContactsDatabaseHelper dbh =
+ ContactsDatabaseHelper.getNewInstanceForTest(getContext(),
+ TestUtils.getContactsDatabaseFilename(getContext()));
+ try {
+ final List<String> list = DatabaseAnalyzer.findTableViewsAllowingColumns(
+ dbh.getReadableDatabase());
+
+ assertTrue(list.contains("contacts"));
+ assertTrue(list.contains("raw_contacts"));
+ assertTrue(list.contains("view_contacts"));
+ assertTrue(list.contains("view_raw_contacts"));
+ assertTrue(list.contains("view_data"));
+
+ assertFalse(list.contains("data"));
+ assertFalse(list.contains("_id"));
+
+ } finally {
+ dbh.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/providers/contacts/sqlite/SqlCheckerTest.java b/tests/src/com/android/providers/contacts/sqlite/SqlCheckerTest.java
new file mode 100644
index 0000000..ee2b5be
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/sqlite/SqlCheckerTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.contacts.sqlite;
+
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+import com.android.providers.contacts.sqlite.SqlChecker.InvalidSqlException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class SqlCheckerTest extends AndroidTestCase {
+ private ArrayList<String> getTokens(String sql) {
+ final ArrayList<String> tokens = new ArrayList<>();
+
+ SqlChecker.findTokens(sql, SqlChecker.OPTION_NONE, token -> tokens.add(token));
+
+ return tokens;
+ }
+
+ private void checkTokens(String sql, String spaceSeparatedExpectedTokens) {
+ final List<String> expected = spaceSeparatedExpectedTokens == null
+ ? new ArrayList<>()
+ : Arrays.asList(spaceSeparatedExpectedTokens.split(" +"));
+
+ assertEquals(expected, getTokens(sql));
+ }
+
+ private void assertInvalidSql(String sql, String message) {
+ try {
+ getTokens(sql);
+ fail("Didn't throw InvalidSqlException");
+ } catch (InvalidSqlException e) {
+ MoreAsserts.assertContainsRegex(message, e.getMessage());
+ }
+ }
+
+ public void testWhitespaces() {
+ checkTokens(" select \t\r\n a\n\n ", "select a");
+ checkTokens("a b", "a b");
+ }
+
+ public void testComment() {
+ checkTokens("--\n", null);
+ checkTokens("a--\n", "a");
+ checkTokens("a--abcdef\n", "a");
+ checkTokens("a--abcdef\nx", "a x");
+ checkTokens("a--\nx", "a x");
+ assertInvalidSql("a--abcdef", "Unterminated comment");
+ assertInvalidSql("a--abcdef\ndef--", "Unterminated comment");
+
+ checkTokens("/**/", null);
+ assertInvalidSql("/*", "Unterminated comment");
+ assertInvalidSql("/*/", "Unterminated comment");
+ assertInvalidSql("/*\n* /*a", "Unterminated comment");
+ checkTokens("a/**/", "a");
+ checkTokens("/**/b", "b");
+ checkTokens("a/**/b", "a b");
+ checkTokens("a/* -- \n* /* **/b", "a b");
+ }
+
+ public void testStrings() {
+ assertInvalidSql("'", "Unterminated quote");
+ assertInvalidSql("a'", "Unterminated quote");
+ assertInvalidSql("a'''", "Unterminated quote");
+ assertInvalidSql("a''' ", "Unterminated quote");
+ checkTokens("''", null);
+ checkTokens("''''", null);
+ checkTokens("a''''b", "a b");
+ checkTokens("a' '' 'b", "a b");
+ checkTokens("'abc'", null);
+ checkTokens("'abc\ndef'", null);
+ checkTokens("a'abc\ndef'", "a");
+ checkTokens("'abc\ndef'b", "b");
+ checkTokens("a'abc\ndef'b", "a b");
+ checkTokens("a'''abc\nd''ef'''b", "a b");
+ }
+
+ public void testDoubleQuotes() {
+ assertInvalidSql("\"", "Unterminated quote");
+ assertInvalidSql("a\"", "Unterminated quote");
+ assertInvalidSql("a\"\"\"", "Unterminated quote");
+ assertInvalidSql("a\"\"\" ", "Unterminated quote");
+ checkTokens("\"\"", "");
+ checkTokens("\"\"\"\"", "\"");
+ checkTokens("a\"\"\"\"b", "a \" b");
+ checkTokens("a\"\t\"\"\t\"b", "a \t\"\t b");
+ checkTokens("\"abc\"", "abc");
+ checkTokens("\"abc\ndef\"", "abc\ndef");
+ checkTokens("a\"abc\ndef\"", "a abc\ndef");
+ checkTokens("\"abc\ndef\"b", "abc\ndef b");
+ checkTokens("a\"abc\ndef\"b", "a abc\ndef b");
+ checkTokens("a\"\"\"abc\nd\"\"ef\"\"\"b", "a \"abc\nd\"ef\" b");
+ }
+
+ public void testBackQuotes() {
+ assertInvalidSql("`", "Unterminated quote");
+ assertInvalidSql("a`", "Unterminated quote");
+ assertInvalidSql("a```", "Unterminated quote");
+ assertInvalidSql("a``` ", "Unterminated quote");
+ checkTokens("``", "");
+ checkTokens("````", "`");
+ checkTokens("a````b", "a ` b");
+ checkTokens("a`\t``\t`b", "a \t`\t b");
+ checkTokens("`abc`", "abc");
+ checkTokens("`abc\ndef`", "abc\ndef");
+ checkTokens("a`abc\ndef`", "a abc\ndef");
+ checkTokens("`abc\ndef`b", "abc\ndef b");
+ checkTokens("a`abc\ndef`b", "a abc\ndef b");
+ checkTokens("a```abc\nd``ef```b", "a `abc\nd`ef` b");
+ }
+
+ public void testBrackets() {
+ assertInvalidSql("[", "Unterminated quote");
+ assertInvalidSql("a[", "Unterminated quote");
+ assertInvalidSql("a[ ", "Unterminated quote");
+ assertInvalidSql("a[[ ", "Unterminated quote");
+ checkTokens("[]", "");
+ checkTokens("[[]", "[");
+ checkTokens("a[[]b", "a [ b");
+ checkTokens("a[\t[\t]b", "a \t[\t b");
+ checkTokens("[abc]", "abc");
+ checkTokens("[abc\ndef]", "abc\ndef");
+ checkTokens("a[abc\ndef]", "a abc\ndef");
+ checkTokens("[abc\ndef]b", "abc\ndef b");
+ checkTokens("a[abc\ndef]b", "a abc\ndef b");
+ checkTokens("a[[abc\nd[ef[]b", "a [abc\nd[ef[ b");
+ }
+
+ public void testSemicolons() {
+ assertInvalidSql(";", "Semicolon is not allowed");
+ assertInvalidSql(" ;", "Semicolon is not allowed");
+ assertInvalidSql("; ", "Semicolon is not allowed");
+ assertInvalidSql("-;-", "Semicolon is not allowed");
+ checkTokens("--;\n", null);
+ checkTokens("/*;*/", null);
+ checkTokens("';'", null);
+ checkTokens("[;]", ";");
+ checkTokens("`;`", ";");
+ }
+
+ public void testTokens() {
+ checkTokens("a,abc,a00b,_1,_123,abcdef", "a abc a00b _1 _123 abcdef");
+ checkTokens("a--\nabc/**/a00b''_1'''ABC'''`_123`abc[d]\"e\"f",
+ "a abc a00b _1 _123 abc d e f");
+ }
+
+ private SqlChecker getChecker(String... tokens) {
+ return new SqlChecker(Arrays.asList(tokens));
+ }
+
+ private void checkEnsureNoInvalidTokens(boolean ok, String sql, String... tokens) {
+ if (ok) {
+ getChecker(tokens).ensureNoInvalidTokens(sql);
+ } else {
+ try {
+ getChecker(tokens).ensureNoInvalidTokens(sql);
+ fail("Should have thrown");
+ } catch (InvalidSqlException e) {
+ // okay
+ }
+ }
+ }
+
+ public void testEnsureNoInvalidTokens() {
+ checkEnsureNoInvalidTokens(true, "a b c", "Select");
+
+ checkEnsureNoInvalidTokens(false, "a b ;c", "Select");
+ checkEnsureNoInvalidTokens(false, "a b seLeCt", "Select");
+
+ checkEnsureNoInvalidTokens(true, "a b select", "x");
+
+ checkEnsureNoInvalidTokens(false, "A b select", "x", "a");
+ checkEnsureNoInvalidTokens(false, "A b select", "a", "x");
+
+ checkEnsureNoInvalidTokens(true, "a /*select*/ b c ", "select");
+ checkEnsureNoInvalidTokens(true, "a 'select' b c ", "select");
+
+ checkEnsureNoInvalidTokens(true, "a b ';' c");
+ checkEnsureNoInvalidTokens(true, "a b /*;*/ c");
+
+ checkEnsureNoInvalidTokens(false, "a b x_ c");
+ checkEnsureNoInvalidTokens(false, "a b [X_OK] c");
+ checkEnsureNoInvalidTokens(true, "a b 'x_' c");
+ checkEnsureNoInvalidTokens(true, "a b /*x_*/ c");
+ }
+
+ private void checkEnsureSingleTokenOnly(boolean ok, String sql, String... tokens) {
+ if (ok) {
+ getChecker(tokens).ensureSingleTokenOnly(sql);
+ } else {
+ try {
+ getChecker(tokens).ensureSingleTokenOnly(sql);
+ fail("Should have thrown");
+ } catch (InvalidSqlException e) {
+ // okay
+ }
+ }
+ }
+
+ public void testEnsureSingleTokenOnly() {
+ checkEnsureSingleTokenOnly(true, "a", "select");
+ checkEnsureSingleTokenOnly(true, "ab", "select");
+ checkEnsureSingleTokenOnly(true, "selec", "select");
+ checkEnsureSingleTokenOnly(true, "selectx", "select");
+
+ checkEnsureSingleTokenOnly(false, "select", "select");
+ checkEnsureSingleTokenOnly(false, "select", "a", "select");
+ checkEnsureSingleTokenOnly(false, "select", "select", "b");
+ checkEnsureSingleTokenOnly(false, "select", "a", "select", "b");
+
+
+ checkEnsureSingleTokenOnly(true, "`a`", "select");
+ checkEnsureSingleTokenOnly(true, "[a]", "select");
+ checkEnsureSingleTokenOnly(true, "\"a\"", "select");
+
+ checkEnsureSingleTokenOnly(false, "'a'", "select");
+
+ checkEnsureSingleTokenOnly(false, "b`a`", "select");
+ checkEnsureSingleTokenOnly(false, "b[a]", "select");
+ checkEnsureSingleTokenOnly(false, "b\"a\"", "select");
+ checkEnsureSingleTokenOnly(false, "b'a'", "select");
+
+ checkEnsureSingleTokenOnly(false, "`a`c", "select");
+ checkEnsureSingleTokenOnly(false, "[a]c", "select");
+ checkEnsureSingleTokenOnly(false, "\"a\"c", "select");
+ checkEnsureSingleTokenOnly(false, "'a'c", "select");
+
+ checkEnsureSingleTokenOnly(false, "", "select");
+ checkEnsureSingleTokenOnly(false, "--", "select");
+ checkEnsureSingleTokenOnly(false, "/**/", "select");
+ checkEnsureSingleTokenOnly(false, " \n", "select");
+ checkEnsureSingleTokenOnly(false, "a--", "select");
+ checkEnsureSingleTokenOnly(false, "a/**/", "select");
+ checkEnsureSingleTokenOnly(false, "a \n", "select");
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java b/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java
index e09e59e..1bfcb17 100644
--- a/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java
+++ b/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java
@@ -94,6 +94,16 @@
assertEquals("\\%test\\%", sb.toString());
}
+ public void testEscapeLikeValuesEscapesEscapes() {
+ StringBuilder sb = new StringBuilder();
+ escapeLikeValue(sb, "my\\test\\string", '\\');
+ assertEquals("my\\\\test\\\\string", sb.toString());
+
+ sb = new StringBuilder();
+ escapeLikeValue(sb, "\\test\\", '\\');
+ assertEquals("\\\\test\\\\", sb.toString());
+ }
+
public void testEscapeLikeValuesNoChanges() {
StringBuilder sb = new StringBuilder();
escapeLikeValue(sb, "my test string", '\\');
diff --git a/tests/src/com/android/providers/contacts/util/MockClock.java b/tests/src/com/android/providers/contacts/util/MockClock.java
index dce06c9..03e8265 100644
--- a/tests/src/com/android/providers/contacts/util/MockClock.java
+++ b/tests/src/com/android/providers/contacts/util/MockClock.java
@@ -42,4 +42,8 @@
public void advance() {
mCurrentTimeMillis++;
}
+
+ public void advanceDay() {
+ mCurrentTimeMillis += 24 * 60 * 60;
+ }
}
diff --git a/tests2/Android.mk b/tests2/Android.mk
new file mode 100644
index 0000000..4209f0e
--- /dev/null
+++ b/tests2/Android.mk
@@ -0,0 +1,39 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ContactsProviderTestUtils \
+ android-support-test \
+ mockito-target-minus-junit4 \
+ legacy-android-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ContactsProviderTests2
+
+LOCAL_INSTRUMENTATION_FOR := ContactsProvider
+LOCAL_CERTIFICATE := shared
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
diff --git a/tests2/AndroidManifest.xml b/tests2/AndroidManifest.xml
new file mode 100644
index 0000000..7678bd2
--- /dev/null
+++ b/tests2/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ Another unit tests against CP2 that runs in a separate process.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.providers.contacts.tests2" >
+
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.providers.contacts.tests2" />
+</manifest>
diff --git a/tests2/src/com/android/providers/contacts/tests2/AllUriTest.java b/tests2/src/com/android/providers/contacts/tests2/AllUriTest.java
new file mode 100644
index 0000000..2aa6a61
--- /dev/null
+++ b/tests2/src/com/android/providers/contacts/tests2/AllUriTest.java
@@ -0,0 +1,718 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.contacts.tests2;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.SyncState;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import junit.framework.AssertionFailedError;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+/*
+ * TODO The following operations would fail, not because they're not supported, but because of
+ * missing parameters. Fix them.
+insert for 'content://com.android.contacts/contacts' failed: Aggregate contacts are created automatically
+insert for 'content://com.android.contacts/raw_contacts/1/data' failed: mimetype is required
+update for 'content://com.android.contacts/raw_contacts/1/stream_items/1' failed: Empty values
+insert for 'content://com.android.contacts/data' failed: raw_contact_id is required
+insert for 'content://com.android.contacts/settings' failed: Must specify both or neither of ACCOUNT_NAME and ACCOUNT_TYPE; URI: content://com.android.contacts/settings?account_type=1, calling user: com.android.providers.contacts.tests2, calling package:com.android.providers.contacts.tests2
+insert for 'content://com.android.contacts/status_updates' failed: PROTOCOL and IM_HANDLE are required
+insert for 'content://com.android.contacts/profile' failed: The profile contact is created automatically
+insert for 'content://com.android.contacts/profile/data' failed: raw_contact_id is required
+insert for 'content://com.android.contacts/profile/raw_contacts/1/data' failed: mimetype is required
+insert for 'content://com.android.contacts/profile/status_updates' failed: PROTOCOL and IM_HANDLE are required
+
+
+openInputStream for 'content://com.android.contacts/contacts/as_multi_vcard/XXX' failed: Caught Exception: Invalid lookup id: XXX
+openInputStream for 'content://com.android.contacts/directory_file_enterprise/XXX?directory=0' failed: Caught Exception: java.lang.IllegalArgumentException: Directory is not a remote directory: content://com.android.contacts/directory_file_enterprise/XXX?directory=0
+openOutputStream for 'content://com.android.contacts/directory_file_enterprise/XXX?directory=0' failed: Caught Exception: java.lang.IllegalArgumentException: Directory is not a remote directory: content://com.android.contacts/directory_file_enterprise/XXX?directory=0
+*/
+
+/**
+ * TODO Add test for delete/update/insert too.
+ * TODO Copy it to CTS
+ */
+@LargeTest
+public class AllUriTest extends AndroidTestCase {
+ private static final String TAG = "AllUrlTest";
+
+ // "-" : Query not supported.
+ // "!" : Can't query because it requires the cross-user permission.
+ // The following markers are planned, but not implemented and the definition below is not all
+ // correct yet.
+ // "d" : supports delete.
+ // "u" : supports update.
+ // "i" : supports insert.
+ // "r" : supports read.
+ // "w" : supports write.
+ // "s" : has x_times_contacted and x_last_time_contacted.
+ // "t" : has x_times_used and x_last_time_used.
+ private static final String[][] URIs = {
+ {"content://com.android.contacts/contacts", "sud"},
+ {"content://com.android.contacts/contacts/1", "sud"},
+ {"content://com.android.contacts/contacts/1/data", "t"},
+ {"content://com.android.contacts/contacts/1/entities", "t"},
+ {"content://com.android.contacts/contacts/1/suggestions"},
+ {"content://com.android.contacts/contacts/1/suggestions/XXX"},
+ {"content://com.android.contacts/contacts/1/photo", "r"},
+ {"content://com.android.contacts/contacts/1/display_photo", "-r"},
+ {"content://com.android.contacts/contacts_corp/1/photo", "-r"},
+ {"content://com.android.contacts/contacts_corp/1/display_photo", "-r"},
+
+ {"content://com.android.contacts/contacts/filter", "s"},
+ {"content://com.android.contacts/contacts/filter/XXX", "s"},
+
+ {"content://com.android.contacts/contacts/lookup/nlookup", "sud"},
+ {"content://com.android.contacts/contacts/lookup/nlookup/data", "t"},
+ {"content://com.android.contacts/contacts/lookup/nlookup/photo", "tr"},
+
+ {"content://com.android.contacts/contacts/lookup/nlookup/1", "sud"},
+ {"content://com.android.contacts/contacts/lookup/nlookup/1/data"},
+ {"content://com.android.contacts/contacts/lookup/nlookup/1/photo", "r"},
+ {"content://com.android.contacts/contacts/lookup/nlookup/display_photo", "-r"},
+ {"content://com.android.contacts/contacts/lookup/nlookup/1/display_photo", "-r"},
+ {"content://com.android.contacts/contacts/lookup/nlookup/entities"},
+ {"content://com.android.contacts/contacts/lookup/nlookup/1/entities"},
+
+ {"content://com.android.contacts/contacts/as_vcard/nlookup", "r"},
+ {"content://com.android.contacts/contacts/as_multi_vcard/XXX"},
+
+ {"content://com.android.contacts/contacts/strequent/", "s"},
+ {"content://com.android.contacts/contacts/strequent/filter/XXX", "s"},
+
+ {"content://com.android.contacts/contacts/group/XXX"},
+
+ {"content://com.android.contacts/contacts/frequent", "s"},
+ {"content://com.android.contacts/contacts/delete_usage", "-d"},
+ {"content://com.android.contacts/contacts/filter_enterprise?directory=0", "s"},
+ {"content://com.android.contacts/contacts/filter_enterprise/XXX?directory=0", "s"},
+
+ {"content://com.android.contacts/raw_contacts", "siud"},
+ {"content://com.android.contacts/raw_contacts/1", "sud"},
+ {"content://com.android.contacts/raw_contacts/1/data", "tu"},
+ {"content://com.android.contacts/raw_contacts/1/display_photo", "-rw"},
+ {"content://com.android.contacts/raw_contacts/1/entity"},
+
+ {"content://com.android.contacts/raw_contact_entities"},
+ {"content://com.android.contacts/raw_contact_entities_corp", "!"},
+
+ {"content://com.android.contacts/data", "tud"},
+ {"content://com.android.contacts/data/1", "tudr"},
+ {"content://com.android.contacts/data/phones", "t"},
+ {"content://com.android.contacts/data_enterprise/phones", "!"},
+ {"content://com.android.contacts/data/phones/1", "tud"},
+ {"content://com.android.contacts/data/phones/filter", "t"},
+ {"content://com.android.contacts/data/phones/filter/XXX", "t"},
+
+ {"content://com.android.contacts/data/phones/filter_enterprise?directory=0", "t"},
+ {"content://com.android.contacts/data/phones/filter_enterprise/XXX?directory=0", "t"},
+
+ {"content://com.android.contacts/data/emails", "t"},
+ {"content://com.android.contacts/data/emails/1", "tud"},
+ {"content://com.android.contacts/data/emails/lookup", "t"},
+ {"content://com.android.contacts/data/emails/lookup/XXX", "t"},
+ {"content://com.android.contacts/data/emails/filter", "t"},
+ {"content://com.android.contacts/data/emails/filter/XXX", "t"},
+ {"content://com.android.contacts/data/emails/filter_enterprise?directory=0", "t"},
+ {"content://com.android.contacts/data/emails/filter_enterprise/XXX?directory=0", "t"},
+ {"content://com.android.contacts/data/emails/lookup_enterprise", "t"},
+ {"content://com.android.contacts/data/emails/lookup_enterprise/XXX", "t"},
+ {"content://com.android.contacts/data/postals", "t"},
+ {"content://com.android.contacts/data/postals/1", "tud"},
+ {"content://com.android.contacts/data/usagefeedback/1,2,3", "-u"},
+ {"content://com.android.contacts/data/callables/", "t"},
+ {"content://com.android.contacts/data/callables/1", "tud"},
+ {"content://com.android.contacts/data/callables/filter", "t"},
+ {"content://com.android.contacts/data/callables/filter/XXX", "t"},
+ {"content://com.android.contacts/data/callables/filter_enterprise?directory=0", "t"},
+ {"content://com.android.contacts/data/callables/filter_enterprise/XXX?directory=0",
+ "t"},
+ {"content://com.android.contacts/data/contactables/", "t"},
+ {"content://com.android.contacts/data/contactables/filter", "t"},
+ {"content://com.android.contacts/data/contactables/filter/XXX", "t"},
+
+ {"content://com.android.contacts/groups", "iud"},
+ {"content://com.android.contacts/groups/1", "ud"},
+ {"content://com.android.contacts/groups_summary"},
+ {"content://com.android.contacts/syncstate", "iud"},
+ {"content://com.android.contacts/syncstate/1", "-ud"},
+ {"content://com.android.contacts/profile/syncstate", "iud"},
+ {"content://com.android.contacts/phone_lookup/XXX"},
+ {"content://com.android.contacts/phone_lookup_enterprise/XXX"},
+ {"content://com.android.contacts/aggregation_exceptions", "u"},
+ {"content://com.android.contacts/settings", "ud"},
+ {"content://com.android.contacts/status_updates", "ud"},
+ {"content://com.android.contacts/status_updates/1"},
+ {"content://com.android.contacts/search_suggest_query"},
+ {"content://com.android.contacts/search_suggest_query/XXX"},
+ {"content://com.android.contacts/search_suggest_shortcut/XXX"},
+ {"content://com.android.contacts/provider_status"},
+ {"content://com.android.contacts/directories", "u"},
+ {"content://com.android.contacts/directories/1"},
+ {"content://com.android.contacts/directories_enterprise"},
+ {"content://com.android.contacts/directories_enterprise/1"},
+ {"content://com.android.contacts/complete_name"},
+ {"content://com.android.contacts/profile", "su"},
+ {"content://com.android.contacts/profile/entities", "s"},
+ {"content://com.android.contacts/profile/data", "tud"},
+ {"content://com.android.contacts/profile/data/1", "td"},
+ {"content://com.android.contacts/profile/photo", "t"},
+ {"content://com.android.contacts/profile/display_photo", "-r"},
+ {"content://com.android.contacts/profile/as_vcard", "r"},
+ {"content://com.android.contacts/profile/raw_contacts", "siud"},
+
+ // Note this should have supported update... Too late to add.
+ {"content://com.android.contacts/profile/raw_contacts/1", "sd"},
+ {"content://com.android.contacts/profile/raw_contacts/1/data", "tu"},
+ {"content://com.android.contacts/profile/raw_contacts/1/entity"},
+ {"content://com.android.contacts/profile/status_updates", "ud"},
+ {"content://com.android.contacts/profile/raw_contact_entities"},
+ {"content://com.android.contacts/display_photo/1", "-r"},
+ {"content://com.android.contacts/photo_dimensions"},
+ {"content://com.android.contacts/deleted_contacts"},
+ {"content://com.android.contacts/deleted_contacts/1"},
+ {"content://com.android.contacts/directory_file_enterprise/XXX?directory=0", "-"},
+ };
+
+ private static final String[] ARG1 = {"-1"};
+
+ private ContentResolver mResolver;
+
+ private ArrayList<String> mFailures;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mFailures = new ArrayList<>();
+ mResolver = getContext().getContentResolver();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mFailures != null) {
+ fail("mFailures is not null. Did you forget to call failIfFailed()?");
+ }
+
+ super.tearDown();
+ }
+
+ private void addFailure(String message, Throwable th) {
+ Log.e(TAG, "Failed: " + message, th);
+
+ final int MAX = 100;
+ if (mFailures.size() == MAX) {
+ mFailures.add("Too many failures.");
+ } else if (mFailures.size() > MAX) {
+ // Too many failures already...
+ } else {
+ mFailures.add(message);
+ }
+ }
+
+ private void failIfFailed() {
+ if (mFailures == null) {
+ fail("mFailures is null. Maybe called failIfFailed() twice?");
+ }
+ if (mFailures.size() > 0) {
+ StringBuilder message = new StringBuilder();
+
+ if (mFailures.size() > 0) {
+ Log.e(TAG, "Something went wrong:");
+ for (String s : mFailures) {
+ Log.e(TAG, s);
+ if (message.length() > 0) {
+ message.append("\n");
+ }
+ message.append(s);
+ }
+ }
+ mFailures = null;
+ fail("Following test(s) failed:\n" + message);
+ }
+ mFailures = null;
+ }
+
+ private static Uri getUri(String[] path) {
+ return Uri.parse(path[0]);
+ }
+
+ private static boolean supportsQuery(String[] path) {
+ if (path.length == 1) {
+ return true; // supports query by default.
+ }
+ return !(path[1].contains("-") || path[1].contains("!"));
+ }
+
+ private static boolean supportsInsert(String[] path) {
+ return (path.length) >= 2 && path[1].contains("i");
+ }
+
+ private static boolean supportsUpdate(String[] path) {
+ return (path.length) >= 2 && path[1].contains("u");
+ }
+
+ private static boolean supportsDelete(String[] path) {
+ return (path.length) >= 2 && path[1].contains("d");
+ }
+
+ private static boolean supportsRead(String[] path) {
+ return (path.length) >= 2 && path[1].contains("r");
+ }
+
+ private static boolean supportsWrite(String[] path) {
+ return (path.length) >= 2 && path[1].contains("w");
+ }
+
+ private String[] getColumns(Uri uri) {
+ try (Cursor c = mResolver.query(uri,
+ null, // projection
+ "1=2", // selection
+ null, // selection args
+ null // sort order
+ )) {
+ return c.getColumnNames();
+ }
+ }
+
+ private void checkQueryExecutable(Uri uri,
+ String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ try {
+ try (Cursor c = mResolver.query(uri, projection, selection,
+ selectionArgs, sortOrder)) {
+ c.moveToFirst();
+ }
+ } catch (Throwable th) {
+ addFailure("Query failed: URI=" + uri + " Message=" + th.getMessage(), th);
+ }
+ try {
+ // With CancellationSignal.
+ try (Cursor c = mResolver.query(uri, projection, selection,
+ selectionArgs, sortOrder, new CancellationSignal())) {
+ c.moveToFirst();
+ }
+ } catch (Throwable th) {
+ addFailure("Query with cancel failed: URI=" + uri + " Message=" + th.getMessage(), th);
+ }
+ try {
+ // With limit.
+ try (Cursor c = mResolver.query(
+ uri.buildUpon().appendQueryParameter(
+ ContactsContract.LIMIT_PARAM_KEY, "0").build(),
+ projection, selection, selectionArgs, sortOrder)) {
+ c.moveToFirst();
+ }
+ } catch (Throwable th) {
+ addFailure("Query with limit failed: URI=" + uri + " Message=" + th.getMessage(), th);
+ }
+
+ try {
+ // With account.
+ try (Cursor c = mResolver.query(
+ uri.buildUpon()
+ .appendQueryParameter(RawContacts.ACCOUNT_NAME, "a")
+ .appendQueryParameter(RawContacts.ACCOUNT_TYPE, "b")
+ .appendQueryParameter(RawContacts.DATA_SET, "c")
+ .build(),
+ projection, selection, selectionArgs, sortOrder)) {
+ c.moveToFirst();
+ }
+ } catch (Throwable th) {
+ addFailure("Query with limit failed: URI=" + uri + " Message=" + th.getMessage(), th);
+ }
+ }
+
+ private void checkQueryNotExecutable(Uri uri,
+ String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ try {
+ try (Cursor c = mResolver.query(uri, projection, selection,
+ selectionArgs, sortOrder)) {
+ c.moveToFirst();
+ }
+ } catch (Throwable th) {
+ // pass.
+ return;
+ }
+ addFailure("Query on " + uri + " expected to fail, but succeeded.", null);
+ }
+
+ /**
+ * Make sure all URLs are accessible with all arguments = null.
+ */
+ public void testSelect() {
+ for (String[] path : URIs) {
+ if (!supportsQuery(path)) continue;
+ final Uri uri = getUri(path);
+
+ checkQueryExecutable(uri, // uri
+ null, // projection
+ null, // selection
+ null, // selection args
+ null // sort order
+ );
+ }
+ failIfFailed();
+ }
+
+ public void testNoHiddenColumns() {
+ for (String[] path : URIs) {
+ if (!supportsQuery(path)) continue;
+ final Uri uri = getUri(path);
+
+ for (String column : getColumns(uri)) {
+ if (column.toLowerCase().startsWith(ContactsContract.HIDDEN_COLUMN_PREFIX)) {
+ addFailure("Uri " + uri + " returned hidden column " + column, null);
+ }
+ }
+ }
+ failIfFailed();
+ }
+
+// Temporarily disabled due to taking too much time.
+// /**
+// * Make sure all URLs are accessible with a projection.
+// */
+// public void testSelectWithProjection() {
+// for (String[] path : URIs) {
+// if (!supportsQuery(path)) continue;
+// final Uri uri = getUri(path);
+//
+// for (String column : getColumns(uri)) {
+// // Some columns are not selectable alone due to bugs, and we don't want to fix them
+// // in order to avoid expanding the differences between versions, so here're some
+// // hacks to make it work...
+//
+// String[] projection = {column};
+//
+// final String u = path[0];
+// if ((u.startsWith("content://com.android.contacts/status_updates")
+// || u.startsWith("content://com.android.contacts/profile/status_updates"))
+// && ("im_handle".equals(column)
+// || "im_account".equals(column)
+// || "protocol".equals(column)
+// || "custom_protocol".equals(column)
+// || "presence_raw_contact_id".equals(column)
+// )) {
+// // These columns only show up when the projection contains certain columns.
+//
+// projection = new String[]{"mode", column};
+// } else if ((u.startsWith("content://com.android.contacts/search_suggest_query")
+// || u.startsWith("content://contacts/search_suggest_query"))
+// && "suggest_intent_action".equals(column)) {
+// // Can't be included in the projection due to a bug in GlobalSearchSupport.
+// continue;
+// } else if (RawContacts.BACKUP_ID.equals(column)) {
+// // Some URIs don't support a projection with BAKCUP_ID only.
+// projection = new String[]{RawContacts.BACKUP_ID, RawContacts.SOURCE_ID};
+// }
+//
+// checkQueryExecutable(uri,
+// projection, // projection
+// null, // selection
+// null, // selection args
+// null // sort order
+// );
+// }
+// }
+// failIfFailed();
+// }
+
+ /**
+ * Make sure all URLs are accessible with a selection.
+ */
+ public void testSelectWithSelection() {
+ for (String[] path : URIs) {
+ if (!supportsQuery(path)) continue;
+ final Uri uri = getUri(path);
+
+ checkQueryExecutable(uri,
+ null, // projection
+ "1=?", // selection
+ ARG1, // , // selection args
+ null // sort order
+ );
+ }
+ failIfFailed();
+ }
+
+// /**
+// * Make sure all URLs are accessible with a selection.
+// */
+// public void testSelectWithSelectionUsingColumns() {
+// for (String[] path : URIs) {
+// if (!supportsQuery(path)) continue;
+// final Uri uri = getUri(path);
+//
+// for (String column : getColumns(uri)) {
+// checkQueryExecutable(uri,
+// null, // projection
+// column + "=?", // selection
+// ARG1, // , // selection args
+// null // sort order
+// );
+// }
+// }
+// failIfFailed();
+// }
+
+// Temporarily disabled due to taking too much time.
+// /**
+// * Make sure all URLs are accessible with an order-by.
+// */
+// public void testSelectWithSortOrder() {
+// for (String[] path : URIs) {
+// if (!supportsQuery(path)) continue;
+// final Uri uri = getUri(path);
+//
+// for (String column : getColumns(uri)) {
+// checkQueryExecutable(uri,
+// null, // projection
+// "1=2", // selection
+// null, // , // selection args
+// column // sort order
+// );
+// }
+// }
+// failIfFailed();
+// }
+
+ /**
+ * Make sure all URLs are accessible with all arguments.
+ */
+ public void testSelectWithAllArgs() {
+ for (String[] path : URIs) {
+ if (!supportsQuery(path)) continue;
+ final Uri uri = getUri(path);
+
+ final String[] projection = {getColumns(uri)[0]};
+
+ checkQueryExecutable(uri,
+ projection, // projection
+ "1=?", // selection
+ ARG1, // , // selection args
+ getColumns(uri)[0] // sort order
+ );
+ }
+ failIfFailed();
+ }
+
+ public void testNonSelect() {
+ for (String[] path : URIs) {
+ if (supportsQuery(path)) continue;
+ final Uri uri = getUri(path);
+
+ checkQueryNotExecutable(uri, // uri
+ null, // projection
+ null, // selection
+ null, // selection args
+ null // sort order
+ );
+ }
+ failIfFailed();
+ }
+
+ private static boolean supportsTimesContacted(String[] path) {
+ return path.length > 1 && path[1].contains("s");
+ }
+
+ private static boolean supportsTimesUsed(String[] path) {
+ return path.length > 1 && path[1].contains("t");
+ }
+
+ private void checkColumnAccessible(Uri uri, String column) {
+ try {
+ try (Cursor c = mResolver.query(
+ uri, new String[]{column}, column + "=0", null, column
+ )) {
+ c.moveToFirst();
+ }
+ } catch (Throwable th) {
+ addFailure("Query failed: URI=" + uri + " Message=" + th.getMessage(), th);
+ }
+ }
+
+ /** Test for {@link #checkColumnAccessible} */
+ public void testCheckColumnAccessible() {
+ checkColumnAccessible(Contacts.CONTENT_URI, "x_times_contacted");
+ try {
+ failIfFailed();
+ } catch (AssertionFailedError expected) {
+ return; // expected.
+ }
+ fail("Failed to detect issue.");
+ }
+
+ private void checkColumnNotAccessibleInner(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ try {
+ try (Cursor c = mResolver.query(uri, projection, selection,
+ selectionArgs, sortOrder)) {
+ c.moveToFirst();
+ }
+ } catch (IllegalArgumentException th) {
+ // pass.
+ return;
+ }
+ addFailure("Query on " + uri +
+ " expected to throw IllegalArgumentException, but succeeded.", null);
+ }
+
+ private void checkColumnNotAccessible(Uri uri, String column) {
+ checkColumnNotAccessibleInner(uri, new String[] {column}, null, null, null);
+ checkColumnNotAccessibleInner(uri, null, column + "=1", null, null);
+ checkColumnNotAccessibleInner(uri, null, null, null, /* order by */ column);
+ }
+
+ /** Test for {@link #checkColumnNotAccessible} */
+ public void testCheckColumnNotAccessible() {
+ checkColumnNotAccessible(Contacts.CONTENT_URI, "times_contacted");
+ try {
+ failIfFailed();
+ } catch (AssertionFailedError expected) {
+ return; // expected.
+ }
+ fail("Failed to detect issue.");
+ }
+
+ /**
+ * Make sure the x_ columns are not accessible.
+ */
+ public void testProhibitedColumns() {
+ for (String[] path : URIs) {
+ final Uri uri = getUri(path);
+ if (supportsTimesContacted(path)) {
+ checkColumnAccessible(uri, "times_contacted");
+ checkColumnAccessible(uri, "last_time_contacted");
+
+ checkColumnNotAccessible(uri, "X_times_contacted");
+ checkColumnNotAccessible(uri, "X_slast_time_contacted");
+ }
+ if (supportsTimesUsed(path)) {
+ checkColumnAccessible(uri, "times_used");
+ checkColumnAccessible(uri, "last_time_used");
+
+ checkColumnNotAccessible(uri, "X_times_used");
+ checkColumnNotAccessible(uri, "X_last_time_used");
+ }
+ }
+ failIfFailed();
+ }
+
+ private void checkExecutable(String operation, Uri uri, boolean shouldWork, Runnable r) {
+ if (shouldWork) {
+ try {
+ r.run();
+ } catch (Exception e) {
+ addFailure(operation + " for '" + uri + "' failed: " + e.getMessage(), e);
+ }
+ } else {
+ try {
+ r.run();
+ addFailure(operation + " for '" + uri + "' NOT failed.", null);
+ } catch (Exception expected) {
+ }
+ }
+ }
+
+ public void testAllOperations() {
+ final ContentValues cv = new ContentValues();
+
+ for (String[] path : URIs) {
+ final Uri uri = getUri(path);
+
+ cv.clear();
+ if (supportsQuery(path)) {
+ cv.put(getColumns(uri)[0], 1);
+ } else {
+ cv.put("_id", 1);
+ }
+ if (uri.toString().contains("syncstate")) {
+ cv.put(SyncState.ACCOUNT_NAME, "abc");
+ cv.put(SyncState.ACCOUNT_TYPE, "def");
+ }
+
+ checkExecutable("insert", uri, supportsInsert(path), () -> {
+ final Uri newUri = mResolver.insert(uri, cv);
+ if (newUri == null) {
+ addFailure("Insert for '" + uri + "' returned null.", null);
+ } else {
+ // "profile/raw_contacts/#" is missing update support. too late to add, so
+ // just skip.
+ if (!newUri.toString().startsWith(
+ "content://com.android.contacts/profile/raw_contacts/")) {
+ checkExecutable("insert -> update", newUri, true, () -> {
+ mResolver.update(newUri, cv, null, null);
+ });
+ }
+ checkExecutable("insert -> delete", newUri, true, () -> {
+ mResolver.delete(newUri, null, null);
+ });
+ }
+ });
+ checkExecutable("update", uri, supportsUpdate(path), () -> {
+ mResolver.update(uri, cv, "1=2", null);
+ });
+ checkExecutable("delete", uri, supportsDelete(path), () -> {
+ mResolver.delete(uri, "1=2", null);
+ });
+ }
+ failIfFailed();
+ }
+
+ public void testAllFileOperations() {
+ for (String[] path : URIs) {
+ final Uri uri = getUri(path);
+
+ checkExecutable("openInputStream", uri, supportsRead(path), () -> {
+ try (InputStream st = mResolver.openInputStream(uri)) {
+ } catch (FileNotFoundException e) {
+ // TODO This happens because we try to read nonexistent photos. Ideally
+ // we should actually check it's readable.
+ if (e.getMessage().contains("Stream I/O not supported")) {
+ throw new RuntimeException("Caught Exception: " + e.toString(), e);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Caught Exception: " + e.toString(), e);
+ }
+ });
+ checkExecutable("openOutputStream", uri, supportsWrite(path), () -> {
+ try (OutputStream st = mResolver.openOutputStream(uri)) {
+ } catch (Exception e) {
+ throw new RuntimeException("Caught Exception: " + e.toString(), e);
+ }
+ });
+ }
+ failIfFailed();
+ }
+}
+
+