aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml24
-rw-r--r--res/values-af/strings.xml2
-rw-r--r--res/values-am/strings.xml2
-rw-r--r--res/values-ar/strings.xml2
-rw-r--r--res/values-az/strings.xml (renamed from res/values-az-rAZ/strings.xml)2
-rw-r--r--res/values-b+sr+Latn/strings.xml2
-rw-r--r--res/values-be/strings.xml35
-rw-r--r--res/values-bg/strings.xml2
-rw-r--r--res/values-bn/strings.xml (renamed from res/values-bn-rBD/strings.xml)6
-rw-r--r--res/values-bs/strings.xml35
-rw-r--r--res/values-ca/strings.xml2
-rw-r--r--res/values-cs/strings.xml2
-rw-r--r--res/values-da/strings.xml4
-rw-r--r--res/values-de/strings.xml2
-rw-r--r--res/values-el/strings.xml6
-rw-r--r--res/values-en-rAU/strings.xml2
-rw-r--r--res/values-en-rGB/strings.xml2
-rw-r--r--res/values-en-rIN/strings.xml2
-rw-r--r--res/values-es-rUS/strings.xml6
-rw-r--r--res/values-es/strings.xml2
-rw-r--r--res/values-et/strings.xml (renamed from res/values-et-rEE/strings.xml)2
-rw-r--r--res/values-eu/strings.xml (renamed from res/values-eu-rES/strings.xml)2
-rw-r--r--res/values-fa/strings.xml4
-rw-r--r--res/values-fi/strings.xml2
-rw-r--r--res/values-fr-rCA/strings.xml2
-rw-r--r--res/values-fr/strings.xml2
-rw-r--r--res/values-gl/strings.xml (renamed from res/values-gl-rES/strings.xml)2
-rw-r--r--res/values-gu/strings.xml (renamed from res/values-gu-rIN/strings.xml)4
-rw-r--r--res/values-hi/strings.xml2
-rw-r--r--res/values-hr/strings.xml2
-rw-r--r--res/values-hu/strings.xml2
-rw-r--r--res/values-hy/strings.xml (renamed from res/values-hy-rAM/strings.xml)2
-rw-r--r--res/values-in/strings.xml2
-rw-r--r--res/values-is/strings.xml (renamed from res/values-is-rIS/strings.xml)2
-rw-r--r--res/values-it/strings.xml2
-rw-r--r--res/values-iw/strings.xml2
-rw-r--r--res/values-ja/strings.xml2
-rw-r--r--res/values-ka/strings.xml (renamed from res/values-ka-rGE/strings.xml)2
-rw-r--r--res/values-kk/strings.xml (renamed from res/values-kk-rKZ/strings.xml)2
-rw-r--r--res/values-km/strings.xml (renamed from res/values-km-rKH/strings.xml)2
-rw-r--r--res/values-kn/strings.xml (renamed from res/values-kn-rIN/strings.xml)4
-rw-r--r--res/values-ko/strings.xml2
-rw-r--r--res/values-ky/strings.xml (renamed from res/values-ky-rKG/strings.xml)14
-rw-r--r--res/values-lo/strings.xml (renamed from res/values-lo-rLA/strings.xml)4
-rw-r--r--res/values-lt/strings.xml2
-rw-r--r--res/values-lv/strings.xml2
-rw-r--r--res/values-mk/strings.xml (renamed from res/values-mk-rMK/strings.xml)2
-rw-r--r--res/values-ml/strings.xml (renamed from res/values-ml-rIN/strings.xml)2
-rw-r--r--res/values-mn/strings.xml (renamed from res/values-mn-rMN/strings.xml)2
-rw-r--r--res/values-mr/strings.xml (renamed from res/values-mr-rIN/strings.xml)2
-rw-r--r--res/values-ms/strings.xml (renamed from res/values-ms-rMY/strings.xml)2
-rw-r--r--res/values-my/strings.xml (renamed from res/values-my-rMM/strings.xml)4
-rw-r--r--res/values-nb/strings.xml2
-rw-r--r--res/values-ne/strings.xml (renamed from res/values-ne-rNP/strings.xml)2
-rw-r--r--res/values-nl/strings.xml2
-rw-r--r--res/values-pa/strings.xml (renamed from res/values-pa-rIN/strings.xml)2
-rw-r--r--res/values-pl/strings.xml2
-rw-r--r--res/values-pt-rBR/strings.xml2
-rw-r--r--res/values-pt-rPT/strings.xml2
-rw-r--r--res/values-pt/strings.xml2
-rw-r--r--res/values-ro/strings.xml14
-rw-r--r--res/values-ru/strings.xml2
-rw-r--r--res/values-si/strings.xml (renamed from res/values-si-rLK/strings.xml)2
-rw-r--r--res/values-sk/strings.xml2
-rw-r--r--res/values-sl/strings.xml2
-rw-r--r--res/values-sq/strings.xml (renamed from res/values-sq-rAL/strings.xml)2
-rw-r--r--res/values-sr/strings.xml2
-rw-r--r--res/values-sv/strings.xml2
-rw-r--r--res/values-sw/strings.xml2
-rw-r--r--res/values-ta/strings.xml (renamed from res/values-ta-rIN/strings.xml)2
-rw-r--r--res/values-te/strings.xml (renamed from res/values-te-rIN/strings.xml)2
-rw-r--r--res/values-th/strings.xml2
-rw-r--r--res/values-tl/strings.xml2
-rw-r--r--res/values-tr/strings.xml2
-rw-r--r--res/values-uk/strings.xml2
-rw-r--r--res/values-ur/strings.xml (renamed from res/values-ur-rPK/strings.xml)2
-rw-r--r--res/values-uz/strings.xml (renamed from res/values-uz-rUZ/strings.xml)2
-rw-r--r--res/values-vi/strings.xml2
-rw-r--r--res/values-zh-rCN/strings.xml4
-rw-r--r--res/values-zh-rHK/strings.xml2
-rw-r--r--res/values-zh-rTW/strings.xml2
-rw-r--r--res/values-zu/strings.xml2
-rwxr-xr-xrun-all-tests.sh41
-rw-r--r--src/com/android/providers/contacts/AbstractContactsProvider.java214
-rw-r--r--src/com/android/providers/contacts/CallLogDatabaseHelper.java40
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java46
-rw-r--r--src/com/android/providers/contacts/ContactDirectoryManager.java144
-rw-r--r--src/com/android/providers/contacts/ContactMetadataProvider.java10
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java2445
-rw-r--r--src/com/android/providers/contacts/ContactsPackageMonitor.java165
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java428
-rw-r--r--src/com/android/providers/contacts/ContactsTaskScheduler.java159
-rw-r--r--src/com/android/providers/contacts/ContactsTransaction.java4
-rw-r--r--src/com/android/providers/contacts/ContactsUpgradeReceiver.java3
-rw-r--r--src/com/android/providers/contacts/GlobalSearchSupport.java2
-rw-r--r--src/com/android/providers/contacts/LegacyApiSupport.java105
-rw-r--r--src/com/android/providers/contacts/PackageIntentReceiver.java57
-rw-r--r--src/com/android/providers/contacts/ProfileAwareUriMatcher.java3
-rw-r--r--src/com/android/providers/contacts/ProfileDatabaseHelper.java26
-rw-r--r--src/com/android/providers/contacts/ProfileProvider.java17
-rw-r--r--src/com/android/providers/contacts/SearchIndexManager.java2
-rw-r--r--src/com/android/providers/contacts/VoicemailCleanupService.java57
-rw-r--r--src/com/android/providers/contacts/VoicemailContentProvider.java188
-rw-r--r--src/com/android/providers/contacts/VoicemailContentTable.java11
-rw-r--r--src/com/android/providers/contacts/VoicemailStatusTable.java9
-rw-r--r--src/com/android/providers/contacts/VoicemailTable.java2
-rw-r--r--src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java33
-rw-r--r--src/com/android/providers/contacts/sqlite/DatabaseAnalyzer.java103
-rw-r--r--src/com/android/providers/contacts/sqlite/SqlChecker.java259
-rw-r--r--src/com/android/providers/contacts/util/DbQueryUtils.java2
-rw-r--r--src/com/android/providers/contacts/util/PackageUtils.java44
-rw-r--r--test_common/Android.mk31
-rw-r--r--test_common/src/com/android/providers/contacts/testutil/CommonDatabaseUtils.java (renamed from tests/src/com/android/providers/contacts/testutil/CommonDatabaseUtils.java)0
-rw-r--r--test_common/src/com/android/providers/contacts/testutil/ContactUtil.java (renamed from tests/src/com/android/providers/contacts/testutil/ContactUtil.java)0
-rw-r--r--test_common/src/com/android/providers/contacts/testutil/DataUtil.java (renamed from tests/src/com/android/providers/contacts/testutil/DataUtil.java)1
-rw-r--r--test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java (renamed from tests/src/com/android/providers/contacts/testutil/DatabaseAsserts.java)0
-rw-r--r--test_common/src/com/android/providers/contacts/testutil/DeletedContactUtil.java (renamed from tests/src/com/android/providers/contacts/testutil/DeletedContactUtil.java)0
-rw-r--r--test_common/src/com/android/providers/contacts/testutil/RawContactUtil.java (renamed from tests/src/com/android/providers/contacts/testutil/RawContactUtil.java)18
-rw-r--r--test_common/src/com/android/providers/contacts/testutil/TestUtil.java (renamed from tests/src/com/android/providers/contacts/testutil/TestUtil.java)11
-rw-r--r--tests/Android.mk7
-rw-r--r--tests/AndroidTest.xml27
-rw-r--r--tests/assets/upgradeTest/pre_upgrade1201.sql27
-rw-r--r--tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java28
-rw-r--r--tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java13
-rw-r--r--tests/src/com/android/providers/contacts/BaseVoicemailProviderTest.java9
-rw-r--r--tests/src/com/android/providers/contacts/CallLogProviderTest.java2
-rw-r--r--tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java64
-rw-r--r--tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java9
-rw-r--r--tests/src/com/android/providers/contacts/ContactsActor.java26
-rw-r--r--tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java178
-rw-r--r--tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java148
-rw-r--r--tests/src/com/android/providers/contacts/ContactsMockPackageManager.java42
-rw-r--r--tests/src/com/android/providers/contacts/ContactsProvider2Test.java328
-rw-r--r--tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java18
-rw-r--r--tests/src/com/android/providers/contacts/ContactsTaskSchedulerTest.java103
-rw-r--r--tests/src/com/android/providers/contacts/DirectoryTest.java10
-rw-r--r--tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java30
-rw-r--r--tests/src/com/android/providers/contacts/PhotoStoreTest.java2
-rw-r--r--tests/src/com/android/providers/contacts/RenamingDelegatingContext.java255
-rw-r--r--tests/src/com/android/providers/contacts/SecondaryUserContactsProvider2.java (renamed from tests/src/com/android/providers/contacts/StandaloneContactsProvider2.java)23
-rw-r--r--tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java93
-rw-r--r--tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java30
-rw-r--r--tests/src/com/android/providers/contacts/SynchronousProfileProvider.java5
-rw-r--r--tests/src/com/android/providers/contacts/TestUtils.java181
-rw-r--r--tests/src/com/android/providers/contacts/VoicemailCleanupServiceTest.java111
-rw-r--r--tests/src/com/android/providers/contacts/VoicemailCleanupTest.java135
-rw-r--r--tests/src/com/android/providers/contacts/VoicemailProviderTest.java2
-rw-r--r--tests/src/com/android/providers/contacts/sqlite/DatabaseAnalyzerTest.java48
-rw-r--r--tests/src/com/android/providers/contacts/sqlite/SqlCheckerTest.java252
-rw-r--r--tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java10
-rw-r--r--tests/src/com/android/providers/contacts/util/MockClock.java4
-rw-r--r--tests2/Android.mk40
-rw-r--r--tests2/AndroidManifest.xml35
-rw-r--r--tests2/AndroidTest.xml27
-rw-r--r--tests2/src/com/android/providers/contacts/tests2/AllUriTest.java718
-rwxr-xr-xtools/contacts-db-schema.sh2
156 files changed, 5117 insertions, 2907 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7829feee..aa1c396f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,7 +34,8 @@
android:exported="true"
android:grantUriPermissions="true"
android:readPermission="android.permission.READ_CONTACTS"
- android:writePermission="android.permission.WRITE_CONTACTS">
+ android:writePermission="android.permission.WRITE_CONTACTS"
+ android:visibleToInstantApps="true">
<path-permission
android:pathPrefix="/search_suggest_query"
android:readPermission="android.permission.GLOBAL_SEARCH" />
@@ -95,33 +96,12 @@
</intent-filter>
</receiver>
- <receiver android:name="PackageIntentReceiver">
- <intent-filter>
- <action android:name="android.intent.action.PACKAGE_ADDED" />
- <data android:scheme="package" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.PACKAGE_REPLACED" />
- <data android:scheme="package" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.PACKAGE_REMOVED" />
- <data android:scheme="package" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.PACKAGE_CHANGED" />
- <data android:scheme="package" />
- </intent-filter>
- </receiver>
-
<receiver android:name="LocaleChangeReceiver">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED"/>
</intent-filter>
</receiver>
- <service android:name="VoicemailCleanupService"/>
-
<activity android:name=".debug.ContactsDumpActivity"
android:label="@string/debug_dump_title"
android:theme="@android:style/Theme.Holo.Dialog"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 15b960d6..b3d1c7e6 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 ad32b654..c16cd152 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 847813bc..d142f75b 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
index 23c08858..9088a5c2 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 ae8b46c9..b9427e4c 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 00000000..0eeb7d44
--- /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 bf3f06b0..fe07e03c 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
index 2b8974d7..dc803da9 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-bs/strings.xml b/res/values-bs/strings.xml
new file mode 100644
index 00000000..c23e9ef4
--- /dev/null
+++ b/res/values-bs/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 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 009aff9c..9802e659 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 e28b255c..489b5b7f 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 cb969329..fde3a2f2 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 4e310c28..4784c4aa 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 c7bbb890..faa014a1 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 2279f29e..c62b699a 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 2279f29e..c62b699a 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 2279f29e..c62b699a 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 fb9e6e13..603e6d60 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 42f75e12..8157199c 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
index 7676f733..d5dbf950 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
index 73da4121..e9f97d2a 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 ae71e760..3266571b 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 36082375..29ce0104 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 20836ac1..cb7f6c8d 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 3e06adc3..8be5ef14 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
index ac851658..121853dc 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
index 16cd70b6..087ccc2b 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 3bf42f62..5fa4021e 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 e9159e70..17ee9285 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 f7ea7327..98e6f0fe 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
index 14475f75..2d31277f 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 171db41b..9a8a599d 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
index 7a6e04f7..7e9feac0 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 1685b2e6..4d52cea0 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 1cbd5219..d9f36661 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 4c95269f..1756ab8a 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
index 3567fd72..28599979 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
index 5d4b6f21..b8cd1676 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
index c7540536..2a2b50ad 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
index 5a67519b..0e37eae9 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 ad1cb753..6e769120 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
index a886e2d6..d8251c52 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
index 35ce0c99..b75c8161 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 eaa968c1..2681de81 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 9227f588..4593240a 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
index 1c3a4cc7..dc1a07a9 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
index a3aa6d1d..dc2abdf9 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
index 8b7ff235..b15c2d18 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
index 778f6fd6..7e6d6054 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
index 10b3e5aa..b638974f 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
index 7745ea9d..a8f2cb96 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 1cf97fd2..d2e13dd6 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
index 0fd39130..e8139577 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 0cfeff55..f2bcb5ab 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
index cfc32d12..12a5c6e5 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 09345425..232b4b79 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 69069573..3ffd077f 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 fcea4e01..beaa2c73 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 69069573..3ffd077f 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 a8814ccc..3e7cb605 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 e3fe19cc..75263280 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
index e5b50a30..906978b0 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 a1e3bf2d..abfa1c55 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 cd34f2db..fe5ea9ef 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
index f3c73173..37830ff6 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 97b65d5b..8230513a 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 12240a40..e1d20231 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 5249091e..920511ab 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
index 22015e67..1116f19f 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
index c385874d..d6cbf41b 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 da135d46..e3b384db 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 ff6df973..713fcb7a 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 69acd87d..17a6f2fe 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 3fd7e3d1..187e4c87 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
index 3d081d74..16e28332 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
index 9cdeea03..ae88a57d 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 f1fa674a..7aa9943c 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 ad4eda67..9114dd41 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 b03d7bc1..291100d8 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 1e07ef19..f259d839 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 1e4adc17..5fa1316e 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 00000000..84a2ce37
--- /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 0e67d103..ebf0a1b9 100644
--- a/src/com/android/providers/contacts/AbstractContactsProvider.java
+++ b/src/com/android/providers/contacts/AbstractContactsProvider.java
@@ -17,7 +17,6 @@
package com.android.providers.contacts;
import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.ContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
@@ -84,7 +83,7 @@ public abstract class AbstractContactsProvider extends ContentProvider
/**
* 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
@@ -130,15 +129,20 @@ public abstract class AbstractContactsProvider extends ContentProvider
protected final SparseLongArray mUpdateInBatchStats = new SparseLongArray();
protected final SparseLongArray mDeleteInBatchStats = new SparseLongArray();
+ private final SparseLongArray mOperationDurationMicroStats = new SparseLongArray();
+
+ private final ThreadLocal<Integer> mOperationNest = ThreadLocal.withInitial(() -> 0);
+ private final ThreadLocal<Long> mOperationStartNs = ThreadLocal.withInitial(() -> 0L);
+
@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;
}
@@ -159,6 +163,12 @@ public abstract class AbstractContactsProvider extends ContentProvider
synchronized (mStatsLock) {
stats.put(callingUid, stats.get(callingUid) + 1);
mAllCallingUids.put(callingUid, true);
+
+ final int nest = mOperationNest.get();
+ mOperationNest.set(nest + 1);
+ if (nest == 0) {
+ mOperationStartNs.set(SystemClock.elapsedRealtimeNanos());
+ }
}
}
@@ -169,6 +179,19 @@ public abstract class AbstractContactsProvider extends ContentProvider
incrementStats(inBatch ? statsInBatch : statsNonBatch);
}
+ protected void finishOperation() {
+ final int callingUid = Binder.getCallingUid();
+ synchronized (mStatsLock) {
+ final int nest = mOperationNest.get();
+ mOperationNest.set(nest - 1);
+ if (nest == 1) {
+ final long duration = SystemClock.elapsedRealtimeNanos() - mOperationStartNs.get();
+ mOperationDurationMicroStats.put(callingUid,
+ mOperationDurationMicroStats.get(callingUid) + duration / 1000L);
+ }
+ }
+ }
+
public ContactsTransaction getCurrentTransaction() {
return mTransactionHolder.get();
}
@@ -176,119 +199,139 @@ public abstract class AbstractContactsProvider extends ContentProvider
@Override
public Uri insert(Uri uri, ContentValues values) {
incrementStats(mInsertStats, mInsertInBatchStats);
- ContactsTransaction transaction = startTransaction(false);
try {
- Uri result = insertInTransaction(uri, values);
- if (result != null) {
- transaction.markDirty();
+ ContactsTransaction transaction = startTransaction(false);
+ try {
+ Uri result = insertInTransaction(uri, values);
+ if (result != null) {
+ transaction.markDirty();
+ }
+ transaction.markSuccessful(false);
+ return result;
+ } finally {
+ endTransaction(false);
}
- transaction.markSuccessful(false);
- return result;
} finally {
- endTransaction(false);
+ finishOperation();
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
incrementStats(mDeleteStats, mDeleteInBatchStats);
- ContactsTransaction transaction = startTransaction(false);
try {
- int deleted = deleteInTransaction(uri, selection, selectionArgs);
- if (deleted > 0) {
- transaction.markDirty();
+ ContactsTransaction transaction = startTransaction(false);
+ try {
+ int deleted = deleteInTransaction(uri, selection, selectionArgs);
+ if (deleted > 0) {
+ transaction.markDirty();
+ }
+ transaction.markSuccessful(false);
+ return deleted;
+ } finally {
+ endTransaction(false);
}
- transaction.markSuccessful(false);
- return deleted;
} finally {
- endTransaction(false);
+ finishOperation();
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
incrementStats(mUpdateStats, mUpdateInBatchStats);
- ContactsTransaction transaction = startTransaction(false);
try {
- int updated = updateInTransaction(uri, values, selection, selectionArgs);
- if (updated > 0) {
- transaction.markDirty();
+ ContactsTransaction transaction = startTransaction(false);
+ try {
+ int updated = updateInTransaction(uri, values, selection, selectionArgs);
+ if (updated > 0) {
+ transaction.markDirty();
+ }
+ transaction.markSuccessful(false);
+ return updated;
+ } finally {
+ endTransaction(false);
}
- transaction.markSuccessful(false);
- return updated;
} finally {
- endTransaction(false);
+ finishOperation();
}
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
incrementStats(mBatchStats);
- ContactsTransaction transaction = startTransaction(true);
- int numValues = values.length;
- int opCount = 0;
try {
- for (int i = 0; i < numValues; i++) {
- insert(uri, values[i]);
- if (++opCount >= BULK_INSERTS_PER_YIELD_POINT) {
- opCount = 0;
- try {
- yield(transaction);
- } catch (RuntimeException re) {
- transaction.markYieldFailed();
- throw re;
+ ContactsTransaction transaction = startTransaction(true);
+ int numValues = values.length;
+ int opCount = 0;
+ try {
+ for (int i = 0; i < numValues; i++) {
+ insert(uri, values[i]);
+ if (++opCount >= BULK_INSERTS_PER_YIELD_POINT) {
+ opCount = 0;
+ try {
+ yield(transaction);
+ } catch (RuntimeException re) {
+ transaction.markYieldFailed();
+ throw re;
+ }
}
}
+ transaction.markSuccessful(true);
+ } finally {
+ endTransaction(true);
}
- transaction.markSuccessful(true);
+ return numValues;
} finally {
- endTransaction(true);
+ finishOperation();
}
- return numValues;
}
@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
incrementStats(mBatchStats);
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "applyBatch: " + operations.size() + " ops");
- }
- int ypCount = 0;
- int opCount = 0;
- ContactsTransaction transaction = startTransaction(true);
try {
- final int numOperations = operations.size();
- final ContentProviderResult[] results = new ContentProviderResult[numOperations];
- for (int i = 0; i < numOperations; i++) {
- if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
- throw new OperationApplicationException(
- "Too many content provider operations between yield points. "
- + "The maximum number of operations per yield point is "
- + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
- }
- final ContentProviderOperation operation = operations.get(i);
- if (i > 0 && operation.isYieldAllowed()) {
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "applyBatch: " + opCount + " ops finished; about to yield...");
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "applyBatch: " + operations.size() + " ops");
+ }
+ int ypCount = 0;
+ int opCount = 0;
+ ContactsTransaction transaction = startTransaction(true);
+ try {
+ final int numOperations = operations.size();
+ final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
+ throw new OperationApplicationException(
+ "Too many content provider operations between yield points. "
+ + "The maximum number of operations per yield point is "
+ + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
}
- opCount = 0;
- try {
- if (yield(transaction)) {
- ypCount++;
+ final ContentProviderOperation operation = operations.get(i);
+ if (i > 0 && operation.isYieldAllowed()) {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "applyBatch: " + opCount + " ops finished; about to yield...");
+ }
+ opCount = 0;
+ try {
+ if (yield(transaction)) {
+ ypCount++;
+ }
+ } catch (RuntimeException re) {
+ transaction.markYieldFailed();
+ throw re;
}
- } catch (RuntimeException re) {
- transaction.markYieldFailed();
- throw re;
}
- }
- results[i] = operation.apply(this, results, i);
+ results[i] = operation.apply(this, results, i);
+ }
+ transaction.markSuccessful(true);
+ return results;
+ } finally {
+ endTransaction(true);
}
- transaction.markSuccessful(true);
- return results;
} finally {
- endTransaction(true);
+ finishOperation();
}
}
@@ -345,8 +388,9 @@ public abstract class AbstractContactsProvider extends ContentProvider
/**
* 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
@@ -425,20 +469,22 @@ public abstract class AbstractContactsProvider extends ContentProvider
synchronized (mStatsLock) {
pw.println();
pw.println(" Client activities:");
- pw.println(" UID Query Insert Update Delete Batch Insert Update Delete:");
+ pw.println(" UID Query Insert Update Delete Batch Insert Update Delete"
+ + " Sec");
for (int i = 0; i < mAllCallingUids.size(); i++) {
- final int pid = mAllCallingUids.keyAt(i);
+ final int uid = mAllCallingUids.keyAt(i);
pw.println(String.format(
- " %-9d %6d %6d %6d %6d %6d %6d %6d %6d",
- pid,
- mQueryStats.get(pid),
- mInsertStats.get(pid),
- mUpdateStats.get(pid),
- mDeleteStats.get(pid),
- mBatchStats.get(pid),
- mInsertInBatchStats.get(pid),
- mUpdateInBatchStats.get(pid),
- mDeleteInBatchStats.get(pid)
+ " %-9d %6d %6d %6d %6d %6d %6d %6d %6d %12.3f",
+ uid,
+ mQueryStats.get(uid),
+ mInsertStats.get(uid),
+ mUpdateStats.get(uid),
+ mDeleteStats.get(uid),
+ mBatchStats.get(uid),
+ mInsertInBatchStats.get(uid),
+ mUpdateInBatchStats.get(uid),
+ mDeleteInBatchStats.get(uid),
+ (mOperationDurationMicroStats.get(uid) / 1000000.0)
));
}
}
diff --git a/src/com/android/providers/contacts/CallLogDatabaseHelper.java b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
index c88b742d..d4ed9304 100644
--- a/src/com/android/providers/contacts/CallLogDatabaseHelper.java
+++ b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
@@ -26,6 +26,8 @@ import android.provider.CallLog.Calls;
import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Status;
import android.provider.VoicemailContract.Voicemails;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -37,7 +39,7 @@ import com.android.providers.contacts.util.PropertyUtils;
public class CallLogDatabaseHelper {
private static final String TAG = "CallLogDatabaseHelper";
- private static final int DATABASE_VERSION = 4;
+ private static final int DATABASE_VERSION = 5;
private static final boolean DEBUG = false; // DON'T SUBMIT WITH TRUE
@@ -149,6 +151,7 @@ public class CallLogDatabaseHelper {
Voicemails.SOURCE_DATA + " TEXT," +
Voicemails.SOURCE_PACKAGE + " TEXT," +
Voicemails.TRANSCRIPTION + " TEXT," +
+ Voicemails.TRANSCRIPTION_STATE + " INTEGER NOT NULL DEFAULT 0," +
Voicemails.STATE + " INTEGER," +
Voicemails.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
Voicemails.DELETED + " INTEGER NOT NULL DEFAULT 0," +
@@ -193,6 +196,10 @@ public class CallLogDatabaseHelper {
if (oldVersion < 4) {
upgradeToVersion4(db);
}
+
+ if (oldVersion < 5) {
+ upgradeToVersion5(db);
+ }
}
}
@@ -262,6 +269,13 @@ public class CallLogDatabaseHelper {
}
/**
+ * Add {@link Voicemails.TRANSCRIPTION_STATE} column to the CallLog database.
+ */
+ private void upgradeToVersion5(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE calls ADD transcription_state INTEGER NOT NULL DEFAULT 0");
+ }
+
+ /**
* Perform the migration from the contacts2.db (of the latest version) to the current calllog/
* voicemail status tables.
*/
@@ -355,6 +369,30 @@ public class CallLogDatabaseHelper {
return ContactsDatabaseHelper.getInstance(mContext).getWritableDatabase();
}
+ public ArraySet<String> selectDistinctColumn(String table, String column) {
+ final ArraySet<String> ret = new ArraySet<>();
+ final SQLiteDatabase db = getReadableDatabase();
+ final Cursor c = db.rawQuery("SELECT DISTINCT "
+ + column
+ + " FROM " + table, null);
+ try {
+ c.moveToPosition(-1);
+ while (c.moveToNext()) {
+ if (c.isNull(0)) {
+ continue;
+ }
+ final String s = c.getString(0);
+
+ if (!TextUtils.isEmpty(s)) {
+ ret.add(s);
+ }
+ }
+ return ret;
+ } finally {
+ c.close();
+ }
+ }
+
@VisibleForTesting
void closeForTest() {
mOpenHelper.close();
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index 9a5b7c48..9b53c0ef 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -33,10 +33,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.CallLog;
@@ -46,11 +42,13 @@ import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.providers.contacts.CallLogDatabaseHelper.DbProperties;
import com.android.providers.contacts.CallLogDatabaseHelper.Tables;
import com.android.providers.contacts.util.SelectionBuilder;
import com.android.providers.contacts.util.UserUtils;
+
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -60,9 +58,9 @@ import java.util.concurrent.CountDownLatch;
* Call log content provider.
*/
public class CallLogProvider extends ContentProvider {
- private static final String TAG = CallLogProvider.class.getSimpleName();
+ private static final String TAG = "CallLogProvider";
- public static final boolean VERBOSE_LOGGING = false; // DO NOT SUBMIT WITH TRUE
+ public static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
private static final int BACKGROUND_TASK_INITIALIZE = 0;
private static final int BACKGROUND_TASK_ADJUST_PHONE_ACCOUNT = 1;
@@ -137,6 +135,7 @@ public class CallLogProvider extends ContentProvider {
sCallsProjectionMap.put(Calls.NEW, Calls.NEW);
sCallsProjectionMap.put(Calls.VOICEMAIL_URI, Calls.VOICEMAIL_URI);
sCallsProjectionMap.put(Calls.TRANSCRIPTION, Calls.TRANSCRIPTION);
+ sCallsProjectionMap.put(Calls.TRANSCRIPTION_STATE, Calls.TRANSCRIPTION_STATE);
sCallsProjectionMap.put(Calls.IS_READ, Calls.IS_READ);
sCallsProjectionMap.put(Calls.CACHED_NAME, Calls.CACHED_NAME);
sCallsProjectionMap.put(Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_TYPE);
@@ -166,8 +165,8 @@ public class CallLogProvider extends ContentProvider {
private static Long sTimeForTestMillis;
- private HandlerThread mBackgroundThread;
- private Handler mBackgroundHandler;
+ private ContactsTaskScheduler mTaskScheduler;
+
private volatile CountDownLatch mReadAccessLatch;
private CallLogDatabaseHelper mDbHelper;
@@ -186,6 +185,11 @@ public class CallLogProvider extends ContentProvider {
@Override
public boolean onCreate() {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "onCreate: " + this.getClass().getSimpleName()
+ + " user=" + android.os.Process.myUserHandle().getIdentifier());
+ }
+
setAppOps(AppOpsManager.OP_READ_CALL_LOG, AppOpsManager.OP_WRITE_CALL_LOG);
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, getProviderName() + ".onCreate start");
@@ -198,19 +202,16 @@ public class CallLogProvider extends ContentProvider {
mVoicemailPermissions = new VoicemailPermissions(context);
mCallLogInsertionHelper = createCallLogInsertionHelper(context);
- mBackgroundThread = new HandlerThread(getProviderName() + "Worker",
- Process.THREAD_PRIORITY_BACKGROUND);
- mBackgroundThread.start();
- mBackgroundHandler = new Handler(mBackgroundThread.getLooper()) {
+ mReadAccessLatch = new CountDownLatch(1);
+
+ mTaskScheduler = new ContactsTaskScheduler(getClass().getSimpleName()) {
@Override
- public void handleMessage(Message msg) {
- performBackgroundTask(msg.what, msg.obj);
+ public void onPerformTask(int taskId, Object arg) {
+ performBackgroundTask(taskId, arg);
}
};
- mReadAccessLatch = new CountDownLatch(1);
-
- scheduleBackgroundTask(BACKGROUND_TASK_INITIALIZE, null);
+ mTaskScheduler.scheduleTask(BACKGROUND_TASK_INITIALIZE, null);
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, getProviderName() + ".onCreate finish");
@@ -452,7 +453,7 @@ public class CallLogProvider extends ContentProvider {
}
void adjustForNewPhoneAccount(PhoneAccountHandle handle) {
- scheduleBackgroundTask(BACKGROUND_TASK_ADJUST_PHONE_ACCOUNT, handle);
+ mTaskScheduler.scheduleTask(BACKGROUND_TASK_ADJUST_PHONE_ACCOUNT, handle);
}
/**
@@ -735,10 +736,6 @@ public class CallLogProvider extends ContentProvider {
}
}
- private void scheduleBackgroundTask(int task, Object arg) {
- mBackgroundHandler.obtainMessage(task, arg).sendToTarget();
- }
-
private void performBackgroundTask(int task, Object arg) {
if (task == BACKGROUND_TASK_INITIALIZE) {
try {
@@ -751,4 +748,9 @@ public class CallLogProvider extends ContentProvider {
adjustForNewPhoneAccountInternal((PhoneAccountHandle) arg);
}
}
+
+ @Override
+ public void shutdown() {
+ mTaskScheduler.shutdownForTest();
+ }
}
diff --git a/src/com/android/providers/contacts/ContactDirectoryManager.java b/src/com/android/providers/contacts/ContactDirectoryManager.java
index 385fa3ff..33e541d3 100644
--- a/src/com/android/providers/contacts/ContactDirectoryManager.java
+++ b/src/com/android/providers/contacts/ContactDirectoryManager.java
@@ -16,6 +16,7 @@
package com.android.providers.contacts;
+import android.annotation.NonNull;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -29,6 +30,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Directory;
import android.text.TextUtils;
@@ -43,6 +45,7 @@ import com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -51,7 +54,7 @@ import java.util.Set;
public class ContactDirectoryManager {
private static final String TAG = "ContactDirectoryManager";
- private static final boolean DEBUG = false; // DON'T SUBMIT WITH TRUE
+ private static final boolean DEBUG = AbstractContactsProvider.VERBOSE_LOGGING;
public static final String CONTACT_DIRECTORY_META_DATA = "android.content.ContactDirectory";
@@ -101,6 +104,8 @@ public class ContactDirectoryManager {
private final Context mContext;
private final PackageManager mPackageManager;
+ private volatile boolean mDirectoriesForceUpdated = false;
+
public ContactDirectoryManager(ContactsProvider2 contactsProvider) {
mContactsProvider = contactsProvider;
mContext = contactsProvider.getContext();
@@ -111,6 +116,10 @@ public class ContactDirectoryManager {
return (ContactsDatabaseHelper) mContactsProvider.getDatabaseHelper();
}
+ public void setDirectoriesForceUpdated(boolean updated) {
+ mDirectoriesForceUpdated = updated;
+ }
+
/**
* Scans through existing directories to see if the cached resource IDs still
* match their original resource names. If not - plays it safe by refreshing all directories.
@@ -120,9 +129,11 @@ public class ContactDirectoryManager {
private boolean areTypeResourceIdsValid() {
SQLiteDatabase db = getDbHelper().getReadableDatabase();
- Cursor cursor = db.query(Tables.DIRECTORIES,
- new String[] { Directory.TYPE_RESOURCE_ID, Directory.PACKAGE_NAME,
- DirectoryColumns.TYPE_RESOURCE_NAME }, null, null, null, null, null);
+ final Cursor cursor = db.rawQuery("SELECT DISTINCT "
+ + Directory.TYPE_RESOURCE_ID + ","
+ + Directory.PACKAGE_NAME + ","
+ + DirectoryColumns.TYPE_RESOURCE_NAME
+ + " FROM " + Tables.DIRECTORIES, null);
try {
while (cursor.moveToNext()) {
int resourceId = cursor.getInt(0);
@@ -131,6 +142,13 @@ public class ContactDirectoryManager {
String storedResourceName = cursor.getString(2);
String resourceName = getResourceNameById(packageName, resourceId);
if (!TextUtils.equals(storedResourceName, resourceName)) {
+ if (DEBUG) {
+ Log.d(TAG, "areTypeResourceIdsValid:"
+ + " resourceId=" + resourceId
+ + " packageName=" + packageName
+ + " storedResourceName=" + storedResourceName
+ + " resourceName=" + resourceName);
+ }
return false;
}
}
@@ -157,25 +175,71 @@ public class ContactDirectoryManager {
}
}
+ private void saveKnownDirectoryProviders(Set<String> packages) {
+ getDbHelper().setProperty(DbProperties.KNOWN_DIRECTORY_PACKAGES,
+ TextUtils.join(",", packages));
+ }
+
+ private boolean haveKnownDirectoryProvidersChanged(Set<String> packages) {
+ final String directoryPackages = TextUtils.join(",", packages);
+ final String prev = getDbHelper().getProperty(DbProperties.KNOWN_DIRECTORY_PACKAGES, "");
+
+ final boolean changed = !Objects.equals(directoryPackages, prev);
+ if (DEBUG) {
+ Log.d(TAG, "haveKnownDirectoryProvidersChanged=" + changed + "\nprev=" + prev
+ + " current=" + directoryPackages);
+ }
+ return changed;
+ }
+
+ @VisibleForTesting
+ boolean isRescanNeeded() {
+ if ("1".equals(SystemProperties.get("debug.cp2.scan_all_packages", "0"))) {
+ Log.w(TAG, "debug.cp2.scan_all_packages set to 1.");
+ return true; // For debugging.
+ }
+ final String scanComplete =
+ getDbHelper().getProperty(DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
+ if (!"1".equals(scanComplete)) {
+ if (DEBUG) {
+ Log.d(TAG, "DIRECTORY_SCAN_COMPLETE is 0.");
+ }
+ return true;
+ }
+ if (haveKnownDirectoryProvidersChanged(getDirectoryProviderPackages(mPackageManager))) {
+ Log.i(TAG, "Directory provider packages have changed.");
+ return true;
+ }
+ return false;
+ }
+
/**
* Scans all packages for directory content providers.
*/
- public void scanAllPackages(boolean rescan) {
- if (rescan || !areTypeResourceIdsValid()) {
- getDbHelper().clearDirectoryScanComplete();
+ public int scanAllPackages(boolean rescan) {
+ if (!areTypeResourceIdsValid()) {
+ rescan = true;
+ Log.i(TAG, "!areTypeResourceIdsValid.");
+ }
+ if (rescan) {
+ getDbHelper().forceDirectoryRescan();
}
- scanAllPackagesIfNeeded();
+ return scanAllPackagesIfNeeded();
}
- private void scanAllPackagesIfNeeded() {
- String scanComplete = getDbHelper().getProperty(DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
- if (!"0".equals(scanComplete)) {
- return;
+ private int scanAllPackagesIfNeeded() {
+ if (!isRescanNeeded()) {
+ return 0;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "scanAllPackagesIfNeeded()");
}
-
final long start = SystemClock.elapsedRealtime();
- int count = scanAllPackages();
+ // Reset directory updated flag to false. If it's changed to true
+ // then we need to rescan directories.
+ mDirectoriesForceUpdated = false;
+ final int count = scanAllPackages();
getDbHelper().setProperty(DbProperties.DIRECTORY_SCAN_COMPLETE, "1");
final long end = SystemClock.elapsedRealtime();
Log.i(TAG, "Discovered " + count + " contact directories in " + (end - start) + "ms");
@@ -183,6 +247,14 @@ public class ContactDirectoryManager {
// Announce the change to listeners of the contacts authority
mContactsProvider.notifyChange(/* syncToNetwork =*/false,
/* syncToMetadataNetwork =*/false);
+
+ // We schedule a rescan if update(DIRECTORIES) is called while we're scanning all packages.
+ if (mDirectoriesForceUpdated) {
+ mDirectoriesForceUpdated = false;
+ mContactsProvider.scheduleRescanDirectories();
+ }
+
+ return count;
}
@VisibleForTesting
@@ -195,34 +267,25 @@ public class ContactDirectoryManager {
return trueFalse != null && Boolean.TRUE.equals(trueFalse);
}
+ @NonNull
+ static private List<ProviderInfo> getDirectoryProviderInfos(PackageManager pm) {
+ return pm.queryContentProviders(null, 0, 0, CONTACT_DIRECTORY_META_DATA);
+ }
+
/**
* @return List of packages that contain a directory provider.
*/
@VisibleForTesting
+ @NonNull
static Set<String> getDirectoryProviderPackages(PackageManager pm) {
final Set<String> ret = Sets.newHashSet();
- final List<PackageInfo> packages = pm.getInstalledPackages(PackageManager.GET_PROVIDERS
- | PackageManager.GET_META_DATA);
- if (packages == null) {
- return ret;
+ if (DEBUG) {
+ Log.d(TAG, "Listing directory provider packages...");
}
- for (PackageInfo packageInfo : packages) {
- if (DEBUG) {
- Log.d(TAG, "package=" + packageInfo.packageName);
- }
- if (packageInfo.providers == null) {
- continue;
- }
- for (ProviderInfo provider : packageInfo.providers) {
- if (DEBUG) {
- Log.d(TAG, "provider=" + provider.authority);
- }
- if (isDirectoryProvider(provider)) {
- Log.d(TAG, "Found " + provider.authority);
- ret.add(provider.packageName);
- }
- }
+
+ for (ProviderInfo provider : getDirectoryProviderInfos(pm)) {
+ ret.add(provider.packageName);
}
if (DEBUG) {
Log.d(TAG, "Found " + ret.size() + " directory provider packages");
@@ -231,8 +294,7 @@ public class ContactDirectoryManager {
return ret;
}
- @VisibleForTesting
- int scanAllPackages() {
+ private int scanAllPackages() {
SQLiteDatabase db = getDbHelper().getWritableDatabase();
insertDefaultDirectory(db);
insertLocalInvisibleDirectory(db);
@@ -251,7 +313,8 @@ public class ContactDirectoryManager {
+ Directory.ACCOUNT_NAME + "=? AND "
+ Directory.ACCOUNT_TYPE + "=?)";
- for (String packageName : getDirectoryProviderPackages(mPackageManager)) {
+ final Set<String> directoryProviderPackages = getDirectoryProviderPackages(mPackageManager);
+ for (String packageName : directoryProviderPackages) {
if (DEBUG) Log.d(TAG, "package=" + packageName);
// getDirectoryProviderPackages() shouldn't return the contacts provider package
@@ -291,6 +354,9 @@ public class ContactDirectoryManager {
int deletedRows = db.delete(Tables.DIRECTORIES, deleteWhereBuilder.toString(),
deleteWhereArgs.toArray(new String[0]));
+
+ saveKnownDirectoryProviders(directoryProviderPackages);
+
Log.i(TAG, "deleted " + deletedRows
+ " stale rows which don't have any relevant directory");
return count;
@@ -299,7 +365,7 @@ public class ContactDirectoryManager {
private void insertDefaultDirectory(SQLiteDatabase db) {
ContentValues values = new ContentValues();
values.put(Directory._ID, Directory.DEFAULT);
- values.put(Directory.PACKAGE_NAME, mContext.getApplicationInfo().packageName);
+ values.put(Directory.PACKAGE_NAME, mContext.getPackageName());
values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
values.put(Directory.TYPE_RESOURCE_ID, R.string.default_directory);
values.put(DirectoryColumns.TYPE_RESOURCE_NAME,
@@ -313,7 +379,7 @@ public class ContactDirectoryManager {
private void insertLocalInvisibleDirectory(SQLiteDatabase db) {
ContentValues values = new ContentValues();
values.put(Directory._ID, Directory.LOCAL_INVISIBLE);
- values.put(Directory.PACKAGE_NAME, mContext.getApplicationInfo().packageName);
+ values.put(Directory.PACKAGE_NAME, mContext.getPackageName());
values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
values.put(Directory.TYPE_RESOURCE_ID, R.string.local_invisible_directory);
values.put(DirectoryColumns.TYPE_RESOURCE_NAME,
diff --git a/src/com/android/providers/contacts/ContactMetadataProvider.java b/src/com/android/providers/contacts/ContactMetadataProvider.java
index 4396ea66..3cf7df2c 100644
--- a/src/com/android/providers/contacts/ContactMetadataProvider.java
+++ b/src/com/android/providers/contacts/ContactMetadataProvider.java
@@ -181,7 +181,7 @@ public class ContactMetadataProvider extends ContentProvider {
ensureCaller();
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
@@ -212,7 +212,7 @@ public class ContactMetadataProvider extends ContentProvider {
ensureCaller();
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
final int matchedUriId = sURIMatcher.match(uri);
int numDeletes = 0;
@@ -260,7 +260,7 @@ public class ContactMetadataProvider extends ContentProvider {
ensureCaller();
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- db.beginTransaction();
+ db.beginTransactionNonExclusive();
try {
final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
@@ -296,7 +296,7 @@ public class ContactMetadataProvider extends ContentProvider {
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 @@ public class ContactMetadataProvider extends ContentProvider {
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 00f08b1c..07741deb 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.BaseColumns;
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.MetadataSyncState;
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.telephony.SubscriptionManager;
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 libcore.icu.ICU;
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,14 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 900-999 Lollipop
* 1000-1099 M
* 1100-1199 N
+ * 1200-1299 O
* </pre>
*/
- static final int DATABASE_VERSION = 1111;
+ static final int DATABASE_VERSION = 1202;
+ private static final int MINIMUM_SUPPORTED_VERSION = 700;
+
+ @VisibleForTesting
+ static final boolean DISALLOW_SUB_QUERIES = false;
public interface Tables {
public static final String CONTACTS = "contacts";
@@ -313,7 +329,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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";
@@ -335,6 +356,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
String ICU_VERSION = "icu_version";
String LOCALE = "locale";
String DATABASE_TIME_CREATED = "database_time_created";
+ String KNOWN_DIRECTORY_PACKAGES = "knownDirectoryPackages";
}
public interface Clauses {
@@ -396,10 +418,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +468,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +738,24 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +804,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +837,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +879,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +925,101 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
}
+ /** 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 (XX)"
+ + " 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 +1039,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +1049,20 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 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 +1075,70 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
/**
- * 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 + ";");
+
+ 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));
+ }
+ }
- db.execSQL("ATTACH DATABASE ':memory:' AS " + DATABASE_PRESENCE + ";");
- db.execSQL("CREATE TABLE IF NOT EXISTS " + DATABASE_PRESENCE + "." + Tables.PRESENCE + " ("+
+ 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 +1152,21 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ ", " + 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 +1205,14 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ ")"
+ " 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 +1220,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
@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 +1228,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
// 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 +1237,20 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +1259,13 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +1297,13 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +1591,13 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +1640,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
createGroupsView(db);
createContactsTriggers(db);
createContactsIndexes(db, false /* we build stats table later */);
+ createPresenceTables(db);
loadNicknameLookupTable(db);
@@ -1607,16 +1657,23 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +1703,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
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 +1886,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +1955,24 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +2038,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +2080,23 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ " 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 +2186,34 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +2375,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
@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!");
-
- 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 + ";");
+ Log.i(TAG,
+ "Upgrading " + DATABASE_NAME + " from version " + oldVersion + " to " + newVersion);
- // 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 +2388,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +2642,22 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
oldVersion = 1111;
}
+ if (isUpgradeRequired(oldVersion, newVersion, 1200)) {
+ createPresenceTables(db);
+ oldVersion = 1200;
+ }
+
+ if (isUpgradeRequired(oldVersion, newVersion, 1201)) {
+ upgradeToVersion1201(db);
+ upgradeViewsAndTriggers = true;
+ oldVersion = 1201;
+ }
+
+ if (isUpgradeRequired(oldVersion, newVersion, 1202)) {
+ upgradeViewsAndTriggers = true;
+ oldVersion = 1202;
+ }
+
// We extracted "calls" and "voicemail_status" at this point, but we can't remove them here
// yet, until CallLogDatabaseHelper moves the data.
@@ -3046,471 +2712,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +2752,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +2818,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +2929,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +3401,35 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +3504,52 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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);
+ }
+
+ /**
+ * 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 void bindString(SQLiteStatement stmt, int index, String value) {
+ private static void bindString(SQLiteStatement stmt, int index, String value) {
if (value == null) {
stmt.bindNull(index);
} else {
@@ -4741,18 +3566,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
/**
- * 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 +3722,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +3775,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +3808,11 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * 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 +3827,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +3852,59 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
"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 +3915,19 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* {@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 +3937,25 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 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 +4119,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
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 +4166,26 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 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 +4342,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
return;
}
- SQLiteStatement nicknameLookupInsert = db.compileStatement("INSERT INTO "
+ final SQLiteStatement nicknameLookupInsert = db.compileStatement("INSERT INTO "
+ Tables.NICKNAME_LOOKUP + "(" + NicknameLookupColumns.NAME + ","
+ NicknameLookupColumns.CLUSTER + ") VALUES (?,?)");
@@ -5590,9 +4352,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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).
@@ -5648,7 +4409,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
PropertyUtils.setProperty(getWritableDatabase(), key, value);
}
- public void clearDirectoryScanComplete() {
+ public void forceDirectoryRescan() {
setProperty(DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
}
@@ -5733,19 +4494,16 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
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 +4512,18 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ 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 +4531,41 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ 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();
-
- if (mStatusAttributionUpdate == null) {
- mStatusAttributionUpdate = getWritableDatabase().compileStatement(
+ statusUpdateAutoTimestamp.bindLong(1, timestamp);
+ bindString(statusUpdateAutoTimestamp, 2, status);
+ statusUpdateAutoTimestamp.bindLong(3, dataId);
+ bindString(statusUpdateAutoTimestamp, 4, status);
+ statusUpdateAutoTimestamp.execute();
+
+ 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 +4765,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
: 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 +4780,20 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +4804,15 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 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 +4820,14 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 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 +4837,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* @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 +4848,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
"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 +4862,29 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
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 +4964,125 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
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<>();
+
+ if (DISALLOW_SUB_QUERIES) {
+ // 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;
+ }
+
+ /**
+ * 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/ContactsPackageMonitor.java b/src/com/android/providers/contacts/ContactsPackageMonitor.java
new file mode 100644
index 00000000..06565cd9
--- /dev/null
+++ b/src/com/android/providers/contacts/ContactsPackageMonitor.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.contacts;
+
+import android.content.BroadcastReceiver;
+import android.content.BroadcastReceiver.PendingResult;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.ContactsContract;
+import android.provider.VoicemailContract;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.providers.contacts.util.PackageUtils;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * - Handles package related broadcasts.
+ * - Also scan changed packages while the process wasn't running using PM.getChangedPackages().
+ */
+public class ContactsPackageMonitor {
+ private static final String TAG = "ContactsPackageMonitor";
+
+ private static final boolean VERBOSE_LOGGING = AbstractContactsProvider.VERBOSE_LOGGING;
+
+ private static final int BACKGROUND_TASK_PACKAGE_EVENT = 0;
+
+ private static ContactsPackageMonitor sInstance;
+
+ private Context mContext;
+
+ /** We run all BG tasks on this thread/handler sequentially. */
+ private final ContactsTaskScheduler mTaskScheduler;
+
+ private static class PackageEventArg {
+ final String packageName;
+ final PendingResult broadcastPendingResult;
+
+ private PackageEventArg(String packageName, PendingResult broadcastPendingResult) {
+ this.packageName = packageName;
+ this.broadcastPendingResult = broadcastPendingResult;
+ }
+ }
+
+ private ContactsPackageMonitor(Context context) {
+ mContext = context; // Can't use the app context due to a bug with shared process.
+
+ // Start the BG thread and register the receiver.
+ mTaskScheduler = new ContactsTaskScheduler(getClass().getSimpleName()) {
+ @Override
+ public void onPerformTask(int taskId, Object arg) {
+ switch (taskId) {
+ case BACKGROUND_TASK_PACKAGE_EVENT:
+ onPackageChanged((PackageEventArg) arg);
+ break;
+ }
+ }
+ };
+ }
+
+ private void start() {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "Starting... user="
+ + android.os.Process.myUserHandle().getIdentifier());
+ }
+
+ registerReceiver();
+ }
+
+ public static synchronized void start(Context context) {
+ if (sInstance == null) {
+ sInstance = new ContactsPackageMonitor(context);
+ sInstance.start();
+ }
+ }
+
+ private void registerReceiver() {
+ final IntentFilter filter = new IntentFilter();
+
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addDataScheme("package");
+
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getData() == null) {
+ return; // Shouldn't happen.
+ }
+ final String changedPackage = intent.getData().getSchemeSpecificPart();
+ final PendingResult result = goAsync();
+
+ mTaskScheduler.scheduleTask(BACKGROUND_TASK_PACKAGE_EVENT,
+ new PackageEventArg(changedPackage, result));
+ }
+ }, filter);
+ }
+
+ private void onPackageChanged(PackageEventArg arg) {
+ try {
+ final String packageName = arg.packageName;
+ if (TextUtils.isEmpty(packageName)) {
+ Log.w(TAG, "Empty package name detected.");
+ return;
+ }
+ if (VERBOSE_LOGGING) Log.d(TAG, "onPackageChanged: Scanning package: " + packageName);
+
+ // First, tell CP2.
+ final ContactsProvider2 provider = getProvider(mContext, ContactsContract.AUTHORITY);
+ if (provider != null) {
+ provider.onPackageChanged(packageName);
+ }
+
+ // Next, if the package is gone, clean up the voicemail.
+ cleanupVoicemail(mContext, packageName);
+ } finally {
+ if (VERBOSE_LOGGING) Log.v(TAG, "Calling PendingResult.finish()...");
+ arg.broadcastPendingResult.finish();
+ }
+ }
+
+ @VisibleForTesting
+ static void cleanupVoicemail(Context context, String packageName) {
+ if (PackageUtils.isPackageInstalled(context, packageName)) {
+ return; // Still installed.
+ }
+ if (VERBOSE_LOGGING) Log.d(TAG, "Cleaning up data for package: " + packageName);
+
+ // Delete both voicemail content and voicemail status entries for this package.
+ final VoicemailContentProvider provider = getProvider(context, VoicemailContract.AUTHORITY);
+ if (provider != null) {
+ provider.removeBySourcePackage(packageName);
+ }
+ }
+
+ private static <T extends ContentProvider> T getProvider(Context context, String authority) {
+ final IContentProvider iprovider = context.getContentResolver().acquireProvider(authority);
+ final ContentProvider provider = ContentProvider.coerceToLocalContentProvider(iprovider);
+ if (provider != null) {
+ return (T) provider;
+ }
+ Slog.wtf(TAG, "Provider for " + authority + " not found");
+ return null;
+ }
+}
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index eb5dcfdd..0d2dd9ed 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -20,6 +20,7 @@ import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.OnAccountsUpdateListener;
import android.annotation.Nullable;
+import android.annotation.WorkerThread;
import android.app.AppOpsManager;
import android.app.SearchManager;
import android.content.ContentProviderOperation;
@@ -56,16 +57,13 @@ import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
-import android.os.Process;
import android.os.RemoteException;
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 +218,13 @@ public class ContactsProvider2 extends AbstractContactsProvider
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";
@@ -251,10 +249,10 @@ public class ContactsProvider2 extends AbstractContactsProvider
private static final int BACKGROUND_TASK_UPGRADE_AGGREGATION_ALGORITHM = 5;
private static final int BACKGROUND_TASK_UPDATE_SEARCH_INDEX = 6;
private static final int BACKGROUND_TASK_UPDATE_PROVIDER_STATUS = 7;
- private static final int BACKGROUND_TASK_UPDATE_DIRECTORIES = 8;
private static final int BACKGROUND_TASK_CHANGE_LOCALE = 9;
private static final int BACKGROUND_TASK_CLEANUP_PHOTOS = 10;
private static final int BACKGROUND_TASK_CLEAN_DELETE_LOG = 11;
+ private static final int BACKGROUND_TASK_RESCAN_DIRECTORY = 12;
protected static final int STATUS_NORMAL = 0;
protected static final int STATUS_UPGRADING = 1;
@@ -313,7 +311,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +608,23 @@ public class ContactsProvider2 extends AbstractContactsProvider
// 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 +677,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
.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 +694,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
.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 +795,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
.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 +823,18 @@ public class ContactsProvider2 extends AbstractContactsProvider
/** 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 +846,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +879,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
.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 +979,16 @@ public class ContactsProvider2 extends AbstractContactsProvider
.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)
@@ -1519,8 +1529,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
private LocaleSet mCurrentLocales;
private int mContactsAccountCount;
- private HandlerThread mBackgroundThread;
- private Handler mBackgroundHandler;
+ private ContactsTaskScheduler mTaskScheduler;
private long mLastPhotoCleanup = 0;
@@ -1539,6 +1548,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
@Override
public boolean onCreate() {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "onCreate user="
+ + android.os.Process.myUserHandle().getIdentifier());
+ }
+
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, "ContactsProvider2.onCreate start");
}
@@ -1575,7 +1589,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
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.
@@ -1588,13 +1602,10 @@ public class ContactsProvider2 extends AbstractContactsProvider
mReadAccessLatch = new CountDownLatch(1);
mWriteAccessLatch = new CountDownLatch(1);
- mBackgroundThread = new HandlerThread("ContactsProviderWorker",
- Process.THREAD_PRIORITY_BACKGROUND);
- mBackgroundThread.start();
- mBackgroundHandler = new Handler(mBackgroundThread.getLooper()) {
+ mTaskScheduler = new ContactsTaskScheduler(getClass().getSimpleName()) {
@Override
- public void handleMessage(Message msg) {
- performBackgroundTask(msg.what, msg.obj);
+ public void onPerformTask(int taskId, Object arg) {
+ performBackgroundTask(taskId, arg);
}
};
@@ -1604,7 +1615,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
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.
@@ -1620,6 +1631,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_PHOTOS);
scheduleBackgroundTask(BACKGROUND_TASK_CLEAN_DELETE_LOG);
+ ContactsPackageMonitor.start(getContext());
+
return true;
}
@@ -1720,11 +1733,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
protected void scheduleBackgroundTask(int task) {
- mBackgroundHandler.sendEmptyMessage(task);
+ scheduleBackgroundTask(task, null);
}
protected void scheduleBackgroundTask(int task, Object arg) {
- mBackgroundHandler.sendMessage(mBackgroundHandler.obtainMessage(task, arg));
+ mTaskScheduler.scheduleTask(task, arg);
}
protected void performBackgroundTask(int task, Object arg) {
@@ -1767,6 +1780,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
break;
}
+ case BACKGROUND_TASK_RESCAN_DIRECTORY: {
+ updateDirectoriesInBackground(true);
+ break;
+ }
+
case BACKGROUND_TASK_UPDATE_LOCALE: {
updateLocaleInBackground();
break;
@@ -1795,13 +1813,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
break;
}
- case BACKGROUND_TASK_UPDATE_DIRECTORIES: {
- if (arg != null) {
- mContactDirectoryManager.onPackageChanged((String) arg);
- }
- break;
- }
-
case BACKGROUND_TASK_CLEANUP_PHOTOS: {
// Check rate limit.
long now = System.currentTimeMillis();
@@ -2053,7 +2064,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
@Override
- public ContactsDatabaseHelper getDatabaseHelper(final Context context) {
+ public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
return ContactsDatabaseHelper.getInstance(context);
}
@@ -2233,6 +2244,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +2258,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +2273,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +2345,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +2471,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
} else {
switchToContactMode();
}
-
- mDbHelper.get().invalidateAllCache();
}
private void updateSearchIndexInTransaction() {
@@ -2608,7 +2624,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +2633,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
protected void setProviderStatus(int status) {
if (mProviderStatus != status) {
mProviderStatus = status;
- getContext().getContentResolver().notifyChange(ProviderStatus.CONTENT_URI, null, false);
+ ContactsDatabaseHelper.notifyProviderStatusChange(getContext());
}
}
@@ -2866,6 +2882,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +2909,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +4004,12 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +4067,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
case PROFILE: {
invalidateFastScrollingIndexCache();
count = updateContactOptions(values, selection, selectionArgs, callerIsSyncAdapter);
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4061,9 +4074,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
invalidateFastScrollingIndexCache();
count = updateContactOptions(db, ContentUris.parseId(uri), values,
callerIsSyncAdapter);
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4126,9 +4136,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
invalidateFastScrollingIndexCache();
selection = appendAccountIdToSelection(uri, selection);
count = updateRawContacts(values, selection, selectionArgs, callerIsSyncAdapter);
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4145,9 +4152,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
count = updateRawContacts(values, RawContacts._ID + "=?", mSelectionArgs1,
callerIsSyncAdapter);
}
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4173,12 +4177,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
case AGGREGATION_EXCEPTIONS: {
- count = updateAggregationException(db, values, callerIsSyncAdapter,
+ count = updateAggregationException(db, values,
/* callerIsMetadataSyncAdapter =*/false);
invalidateFastScrollingIndexCache();
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
break;
}
@@ -4238,16 +4239,14 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
case DIRECTORIES: {
+ mContactDirectoryManager.setDirectoriesForceUpdated(true);
scanPackagesByUid(Binder.getCallingUid());
count = 1;
break;
}
case DATA_USAGE_FEEDBACK_ID: {
- count = handleDataUsageFeedback(uri, callerIsSyncAdapter) ? 1 : 0;
- if (count > 0) {
- mSyncToNetwork |= !callerIsSyncAdapter;
- }
+ count = handleDataUsageFeedback(uri) ? 1 : 0;
break;
}
@@ -4516,11 +4515,42 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +4626,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
if (shouldMarkMetadataDirtyForRawContact(values)) {
mTransactionContext.get().markRawContactMetadataDirty(
rawContactId, callerIsMetadataSyncAdapter);
- mTransactionContext.get().markRawContactDirtyAndChanged(
- rawContactId, callerIsSyncAdapter);
}
if (isBackupIdChanging) {
Cursor cursor = db.query(Tables.RAW_CONTACTS,
@@ -4755,6 +4783,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +4793,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +4812,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +4857,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +4875,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +4884,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
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 +4928,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
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;
@@ -4916,6 +4943,10 @@ public class ContactsProvider2 extends AbstractContactsProvider
scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_ACCOUNTS);
}
+ public void scheduleRescanDirectories() {
+ scheduleBackgroundTask(BACKGROUND_TASK_RESCAN_DIRECTORY);
+ }
+
interface RawContactsBackupQuery {
String TABLE = Tables.RAW_CONTACTS;
String[] COLUMNS = new String[] {
@@ -5085,8 +5116,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +5139,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +5157,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
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);
}
}
@@ -5440,8 +5469,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
}
+ @WorkerThread
public void onPackageChanged(String packageName) {
- scheduleBackgroundTask(BACKGROUND_TASK_UPDATE_DIRECTORIES, packageName);
+ mContactDirectoryManager.onPackageChanged(packageName);
}
private void removeStaleAccountRows(String table, String accountNameColumn,
@@ -5488,6 +5518,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
" 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)) {
@@ -5505,12 +5540,15 @@ public class ContactsProvider2 extends AbstractContactsProvider
cancellationSignal);
}
incrementStats(mQueryStats);
+ try {
+ // Otherwise proceed with a normal query against the contacts DB.
+ switchToContactMode();
- // Otherwise proceed with a normal query against the contacts DB.
- switchToContactMode();
-
- return queryDirectoryIfNecessary(uri, projection, selection, selectionArgs, sortOrder,
- cancellationSignal);
+ return queryDirectoryIfNecessary(uri, projection, selection, selectionArgs, sortOrder,
+ cancellationSignal);
+ } finally {
+ finishOperation();
+ }
}
private boolean isCallerFromSameUser() {
@@ -5560,6 +5598,35 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +5646,11 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +5665,14 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +5711,10 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +6013,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +6037,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
// 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 +6071,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
// 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 +6124,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
// 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 +7076,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +8045,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +8432,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +8443,21 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +8887,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
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 +8899,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
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 +8911,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
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 +8959,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +9378,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +9784,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
// 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 +9837,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +9876,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
final long rid = cursor.getLong(0);
mTransactionContext.get().markRawContactMetadataDirty(rid,
/* isMetadataSyncAdapter =*/false);
- mTransactionContext.get().markRawContactDirtyAndChanged(rid, callerIsSyncAdapter);
rawContactIds.add(rid);
}
} finally {
@@ -9781,15 +9886,15 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +9941,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +9954,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +9968,14 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
/**
- * 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 +9991,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +10003,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
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 +10225,19 @@ public class ContactsProvider2 extends AbstractContactsProvider
public void switchToProfileModeForTest() {
switchToProfileMode();
}
+
+ @Override
+ public void shutdown() {
+ mTaskScheduler.shutdownForTest();
+ }
+
+ @VisibleForTesting
+ public ContactsDatabaseHelper getContactsDatabaseHelperForTest() {
+ return mContactsHelper;
+ }
+
+ @VisibleForTesting
+ public ProfileProvider getProfileProviderForTest() {
+ return mProfileProvider;
+ }
}
diff --git a/src/com/android/providers/contacts/ContactsTaskScheduler.java b/src/com/android/providers/contacts/ContactsTaskScheduler.java
new file mode 100644
index 00000000..16283877
--- /dev/null
+++ b/src/com/android/providers/contacts/ContactsTaskScheduler.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.contacts;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Runs tasks in a worker thread, which is created on-demand and shuts down after a timeout.
+ */
+public abstract class ContactsTaskScheduler {
+ private static final String TAG = "ContactsTaskScheduler";
+
+ public static final boolean VERBOSE_LOGGING = AbstractContactsProvider.VERBOSE_LOGGING;
+
+ private static final int SHUTDOWN_TIMEOUT_SECONDS = 60;
+
+ private final AtomicInteger mThreadSequenceNumber = new AtomicInteger();
+
+ private final Object mLock = new Object();
+
+ /**
+ * Name of this scheduler for logging.
+ */
+ private final String mName;
+
+ @GuardedBy("mLock")
+ private HandlerThread mThread;
+
+ @GuardedBy("mLock")
+ private MyHandler mHandler;
+
+ private final int mShutdownTimeoutSeconds;
+
+ public ContactsTaskScheduler(String name) {
+ this(name, SHUTDOWN_TIMEOUT_SECONDS);
+ }
+
+ /** With explicit timeout seconds, for testing. */
+ protected ContactsTaskScheduler(String name, int shutdownTimeoutSeconds) {
+ mName = name;
+ mShutdownTimeoutSeconds = shutdownTimeoutSeconds;
+ }
+
+ private class MyHandler extends Handler {
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "[" + mName + "] " + mThread + " dispatching " + msg.what);
+ }
+ onPerformTask(msg.what, msg.obj);
+ }
+ }
+
+ private final Runnable mQuitter = () -> {
+ synchronized (mLock) {
+ stopThread(/* joinOnlyForTest=*/ false);
+ }
+ };
+
+ private boolean isRunning() {
+ synchronized (mLock) {
+ return mThread != null;
+ }
+ }
+
+ /** Schedule a task with no arguments. */
+ @VisibleForTesting
+ public void scheduleTask(int taskId) {
+ scheduleTask(taskId, null);
+ }
+
+ /** Schedule a task with an argument. */
+ @VisibleForTesting
+ public void scheduleTask(int taskId, Object arg) {
+ synchronized (mLock) {
+ if (!isRunning()) {
+ mThread = new HandlerThread("Worker-" + mThreadSequenceNumber.incrementAndGet());
+ mThread.start();
+ mHandler = new MyHandler(mThread.getLooper());
+
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "[" + mName + "] " + mThread + " started.");
+ }
+ }
+ if (arg == null) {
+ mHandler.sendEmptyMessage(taskId);
+ } else {
+ mHandler.sendMessage(mHandler.obtainMessage(taskId, arg));
+ }
+
+ // Schedule thread shutdown.
+ mHandler.removeCallbacks(mQuitter);
+ mHandler.postDelayed(mQuitter, mShutdownTimeoutSeconds * 1000);
+ }
+ }
+
+ public abstract void onPerformTask(int taskId, Object arg);
+
+ @VisibleForTesting
+ public void shutdownForTest() {
+ stopThread(/* joinOnlyForTest=*/ true);
+ }
+
+ private void stopThread(boolean joinOnlyForTest) {
+ synchronized (mLock) {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "[" + mName + "] " + mThread + " stopping...");
+ }
+ if (mThread != null) {
+ mThread.quit();
+ if (joinOnlyForTest) {
+ try {
+ mThread.join();
+ } catch (InterruptedException ignore) {
+ }
+ }
+ }
+ mThread = null;
+ mHandler = null;
+ }
+ }
+
+ @VisibleForTesting
+ public int getThreadSequenceNumber() {
+ return mThreadSequenceNumber.get();
+ }
+
+ @VisibleForTesting
+ public boolean isRunningForTest() {
+ return isRunning();
+ }
+}
diff --git a/src/com/android/providers/contacts/ContactsTransaction.java b/src/com/android/providers/contacts/ContactsTransaction.java
index c6c11d99..e220dd92 100644
--- a/src/com/android/providers/contacts/ContactsTransaction.java
+++ b/src/com/android/providers/contacts/ContactsTransaction.java
@@ -113,9 +113,9 @@ public class ContactsTransaction {
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 57c0cd0c..6f50a145 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;
@@ -87,7 +86,7 @@ public class ContactsUpgradeReceiver extends BroadcastReceiver {
Log.i(TAG, "Creating or opening contacts database");
helper.getWritableDatabase();
- helper.clearDirectoryScanComplete();
+ helper.forceDirectoryRescan();
profileHelper.getWritableDatabase();
calllogHelper.getWritableDatabase();
diff --git a/src/com/android/providers/contacts/GlobalSearchSupport.java b/src/com/android/providers/contacts/GlobalSearchSupport.java
index 20307d49..6678b954 100644
--- a/src/com/android/providers/contacts/GlobalSearchSupport.java
+++ b/src/com/android/providers/contacts/GlobalSearchSupport.java
@@ -232,7 +232,7 @@ public class GlobalSearchSupport {
+ 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 598a4a06..741639a2 100644
--- a/src/com/android/providers/contacts/LegacyApiSupport.java
+++ b/src/com/android/providers/contacts/LegacyApiSupport.java
@@ -107,10 +107,6 @@ public class LegacyApiSupport {
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 @@ public class LegacyApiSupport {
+ " 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 @@ public class LegacyApiSupport {
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 @@ public class LegacyApiSupport {
+ " 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 @@ public class LegacyApiSupport {
int count = 0;
switch(match) {
case PEOPLE_UPDATE_CONTACT_TIME: {
- count = updateContactTime(uri, values);
+ count = 0; // No longer supported.
break;
}
@@ -1077,11 +1048,6 @@ public class LegacyApiSupport {
}
}
- if (values.containsKey(People.LAST_TIME_CONTACTED) &&
- !values.containsKey(People.TIMES_CONTACTED)) {
- updateContactTime(rawContactId, values);
- }
-
return count;
}
@@ -1143,35 +1109,6 @@ public class LegacyApiSupport {
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 @@ public class LegacyApiSupport {
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 @@ public class LegacyApiSupport {
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 @@ public class LegacyApiSupport {
* 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
deleted file mode 100644
index 57362491..00000000
--- a/src/com/android/providers/contacts/PackageIntentReceiver.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.providers.contacts;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentProvider;
-import android.content.Context;
-import android.content.IContentProvider;
-import android.content.Intent;
-import android.net.Uri;
-import android.provider.ContactsContract;
-
-/**
- * Package intent receiver that invokes {@link ContactsProvider2#onPackageChanged} to update
- * the contact directory list.
- */
-public class PackageIntentReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Uri packageUri = intent.getData();
- String packageName = packageUri.getSchemeSpecificPart();
- IContentProvider iprovider =
- context.getContentResolver().acquireProvider(ContactsContract.AUTHORITY);
- ContentProvider provider = ContentProvider.coerceToLocalContentProvider(iprovider);
- if (provider instanceof ContactsProvider2) {
- ((ContactsProvider2)provider).onPackageChanged(packageName);
- }
- handlePackageChangedForVoicemail(context, intent);
- }
-
- private void handlePackageChangedForVoicemail(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED) &&
- !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- // Forward the intent to the cleanup service for handling the event.
- Intent intentToForward = new Intent(context, VoicemailCleanupService.class);
- intentToForward.setData(intent.getData());
- intentToForward.setAction(intent.getAction());
- intentToForward.putExtras(intent.getExtras());
- context.startService(intentToForward);
- }
- }
-}
diff --git a/src/com/android/providers/contacts/ProfileAwareUriMatcher.java b/src/com/android/providers/contacts/ProfileAwareUriMatcher.java
index ee5ec03c..744addc1 100644
--- a/src/com/android/providers/contacts/ProfileAwareUriMatcher.java
+++ b/src/com/android/providers/contacts/ProfileAwareUriMatcher.java
@@ -123,6 +123,9 @@ public class ProfileAwareUriMatcher extends UriMatcher {
}
} 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 a23e5217..966ee7e8 100644
--- a/src/com/android/providers/contacts/ProfileDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ProfileDatabaseHelper.java
@@ -42,18 +42,20 @@ public class ProfileDatabaseHelper extends ContactsDatabaseHelper {
* 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 @@ public class ProfileDatabaseHelper extends ContactsDatabaseHelper {
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 88ae4c3a..6c84e4b0 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -48,10 +48,14 @@ public class ProfileProvider extends AbstractContactsProvider {
}
@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();
@@ -67,8 +71,12 @@ public class ProfileProvider extends AbstractContactsProvider {
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder, CancellationSignal cancellationSignal) {
incrementStats(mQueryStats);
- return mDelegate.queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1,
- cancellationSignal);
+ try {
+ return mDelegate.queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1,
+ cancellationSignal);
+ } finally {
+ finishOperation();
+ }
}
@Override
@@ -149,6 +157,9 @@ public class ProfileProvider extends AbstractContactsProvider {
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 ba2a60d0..768fb978 100644
--- a/src/com/android/providers/contacts/SearchIndexManager.java
+++ b/src/com/android/providers/contacts/SearchIndexManager.java
@@ -113,7 +113,7 @@ public class SearchIndexManager {
@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/VoicemailCleanupService.java b/src/com/android/providers/contacts/VoicemailCleanupService.java
deleted file mode 100644
index 4ad1406a..00000000
--- a/src/com/android/providers/contacts/VoicemailCleanupService.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2011 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.app.IntentService;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.provider.VoicemailContract.Status;
-import android.provider.VoicemailContract.Voicemails;
-import android.util.Log;
-
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * A service that cleans up voicemail related data for packages that are uninstalled.
- */
-public class VoicemailCleanupService extends IntentService {
- private static final String TAG = "VoicemailCleanupService";
-
- public VoicemailCleanupService() {
- super("VoicemailCleanupService");
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- handleIntentInternal(intent, getContentResolver());
- }
-
- @VisibleForTesting
- void handleIntentInternal(Intent intent,
- ContentResolver contentResolver) {
- if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED) &&
- !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- String packageUninstalled = intent.getData().getSchemeSpecificPart();
- Log.d(TAG, "Cleaning up data for package: " + packageUninstalled);
- // Delete both voicemail content and voicemail status entries for this package.
- contentResolver.delete(Voicemails.buildSourceUri(packageUninstalled), null, null);
- contentResolver.delete(Status.buildSourceUri(packageUninstalled), null, null);
- } else {
- Log.w(TAG, "Unexpected intent: " + intent);
- }
- }
-}
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index 099e924d..160a1a99 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -16,11 +16,13 @@
package com.android.providers.contacts;
import static android.provider.VoicemailContract.SOURCE_PACKAGE_FIELD;
+
import static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses;
import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
import android.app.AppOpsManager;
import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -30,15 +32,24 @@ import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Status;
import android.provider.VoicemailContract.Voicemails;
+import android.util.ArraySet;
import android.util.Log;
+
import com.android.providers.contacts.CallLogDatabaseHelper.Tables;
import com.android.providers.contacts.util.ContactsPermissions;
+import com.android.providers.contacts.util.PackageUtils;
import com.android.providers.contacts.util.SelectionBuilder;
import com.android.providers.contacts.util.TypedUriMatcherImpl;
+import com.android.providers.contacts.util.UserUtils;
+
import com.google.common.annotations.VisibleForTesting;
+
import java.io.FileNotFoundException;
+import java.util.Arrays;
import java.util.List;
+import java.util.Set;
/**
* An implementation of the Voicemail content provider. This class in the entry point for both
@@ -48,12 +59,24 @@ import java.util.List;
*/
public class VoicemailContentProvider extends ContentProvider
implements VoicemailTable.DelegateHelper {
+ private static final String TAG = "VoicemailProvider";
+
+ public static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final int BACKGROUND_TASK_SCAN_STALE_PACKAGES = 0;
+
+ private ContactsTaskScheduler mTaskScheduler;
+
private VoicemailPermissions mVoicemailPermissions;
private VoicemailTable.Delegate mVoicemailContentTable;
private VoicemailTable.Delegate mVoicemailStatusTable;
@Override
public boolean onCreate() {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "onCreate: " + this.getClass().getSimpleName()
+ + " user=" + android.os.Process.myUserHandle().getIdentifier());
+ }
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.INFO)) {
Log.i(Constants.PERFORMANCE_TAG, "VoicemailContentProvider.onCreate start");
}
@@ -68,6 +91,18 @@ public class VoicemailContentProvider extends ContentProvider
getDatabaseHelper(context), this, createCallLogInsertionHelper(context));
mVoicemailStatusTable = new VoicemailStatusTable(Tables.VOICEMAIL_STATUS, context,
getDatabaseHelper(context), this);
+
+ mTaskScheduler = new ContactsTaskScheduler(getClass().getSimpleName()) {
+ @Override
+ public void onPerformTask(int taskId, Object arg) {
+ performBackgroundTask(taskId, arg);
+ }
+ };
+
+ scheduleScanStalePackages();
+
+ ContactsPackageMonitor.start(getContext());
+
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.INFO)) {
Log.i(Constants.PERFORMANCE_TAG, "VoicemailContentProvider.onCreate finish");
}
@@ -75,6 +110,16 @@ public class VoicemailContentProvider extends ContentProvider
}
@VisibleForTesting
+ void scheduleScanStalePackages() {
+ scheduleTask(BACKGROUND_TASK_SCAN_STALE_PACKAGES, null);
+ }
+
+ @VisibleForTesting
+ void scheduleTask(int taskId, Object arg) {
+ mTaskScheduler.scheduleTask(taskId, arg);
+ }
+
+ @VisibleForTesting
/*package*/ CallLogInsertionHelper createCallLogInsertionHelper(Context context) {
return DefaultCallLogInsertionHelper.getInstance(context);
}
@@ -103,6 +148,10 @@ public class VoicemailContentProvider extends ContentProvider
@Override
public Uri insert(Uri uri, ContentValues values) {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "insert: uri=" + uri + " values=[" + values + "]" +
+ " CPID=" + Binder.getCallingPid());
+ }
UriData uriData = checkPermissionsAndCreateUriDataForWrite(uri, values);
return getTableDelegate(uriData).insert(uriData, values);
}
@@ -110,6 +159,12 @@ public class VoicemailContentProvider extends ContentProvider
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "query: uri=" + uri + " projection=" + Arrays.toString(projection) +
+ " selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
+ " order=[" + sortOrder + "] CPID=" + Binder.getCallingPid() +
+ " User=" + UserUtils.getCurrentUserHandle(getContext()));
+ }
UriData uriData = checkPermissionsAndCreateUriDataForRead(uri);
SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
selectionBuilder.addClause(getPackageRestrictionClause(true/*isQuery*/));
@@ -119,6 +174,12 @@ public class VoicemailContentProvider extends ContentProvider
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "update: uri=" + uri +
+ " selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
+ " values=[" + values + "] CPID=" + Binder.getCallingPid() +
+ " User=" + UserUtils.getCurrentUserHandle(getContext()));
+ }
UriData uriData = checkPermissionsAndCreateUriDataForWrite(uri, values);
SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
selectionBuilder.addClause(getPackageRestrictionClause(false/*isQuery*/));
@@ -128,6 +189,12 @@ public class VoicemailContentProvider extends ContentProvider
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "delete: uri=" + uri +
+ " selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
+ " CPID=" + Binder.getCallingPid() +
+ " User=" + UserUtils.getCurrentUserHandle(getContext()));
+ }
UriData uriData = checkPermissionsAndCreateUriDataForWrite(uri);
SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
selectionBuilder.addClause(getPackageRestrictionClause(false/*isQuery*/));
@@ -136,14 +203,25 @@ public class VoicemailContentProvider extends ContentProvider
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- UriData uriData = null;
- if (mode.equals("r")) {
- uriData = checkPermissionsAndCreateUriDataForRead(uri);
- } else {
- uriData = checkPermissionsAndCreateUriDataForWrite(uri);
+ boolean success = false;
+ try {
+ UriData uriData = null;
+ if (mode.equals("r")) {
+ uriData = checkPermissionsAndCreateUriDataForRead(uri);
+ } else {
+ uriData = checkPermissionsAndCreateUriDataForWrite(uri);
+ }
+ // openFileHelper() relies on "_data" column to be populated with the file path.
+ final ParcelFileDescriptor ret = getTableDelegate(uriData).openFile(uriData, mode);
+ success = true;
+ return ret;
+ } finally {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "openFile uri=" + uri + " mode=" + mode + " success=" + success +
+ " CPID=" + Binder.getCallingPid() +
+ " User=" + UserUtils.getCurrentUserHandle(getContext()));
+ }
}
- // openFileHelper() relies on "_data" column to be populated with the file path.
- return getTableDelegate(uriData).openFile(uriData, mode);
}
/** Returns the correct table delegate object that can handle this URI. */
@@ -171,7 +249,7 @@ public class VoicemailContentProvider extends ContentProvider
private final String mSourcePackage;
private final VoicemailUriType mUriType;
- public UriData(Uri uri, VoicemailUriType uriType, String id, String sourcePackage) {
+ private UriData(Uri uri, VoicemailUriType uriType, String id, String sourcePackage) {
mUriType = uriType;
mUri = uri;
mId = id;
@@ -248,13 +326,13 @@ public class VoicemailContentProvider extends ContentProvider
// If content values don't contain the provider, calculate the right provider to use.
if (!values.containsKey(SOURCE_PACKAGE_FIELD)) {
String provider = uriData.hasSourcePackage() ?
- uriData.getSourcePackage() : getCallingPackage_();
+ uriData.getSourcePackage() : getInjectedCallingPackage();
values.put(SOURCE_PACKAGE_FIELD, provider);
}
// You must have access to the provider given in values.
if (!mVoicemailPermissions.callerHasWriteAccess(getCallingPackage())) {
- checkPackagesMatch(getCallingPackage_(),
+ checkPackagesMatch(getInjectedCallingPackage(),
values.getAsString(VoicemailContract.SOURCE_PACKAGE_FIELD),
uriData.getUri());
}
@@ -357,47 +435,17 @@ public class VoicemailContentProvider extends ContentProvider
throw new SecurityException(String.format(
"Provider %s does not have %s permission." +
"\nPlease set query parameter '%s' in the URI.\nURI: %s",
- getCallingPackage_(), android.Manifest.permission.WRITE_VOICEMAIL,
+ getInjectedCallingPackage(), android.Manifest.permission.WRITE_VOICEMAIL,
VoicemailContract.PARAM_KEY_SOURCE_PACKAGE, uriData.getUri()));
}
- checkPackagesMatch(getCallingPackage_(), uriData.getSourcePackage(), uriData.getUri());
+ checkPackagesMatch(getInjectedCallingPackage(), uriData.getSourcePackage(),
+ uriData.getUri());
}
}
- /**
- * Gets the name of the calling package.
- * <p>
- * It's possible (though unlikely) for there to be more than one calling package (requires that
- * your manifest say you want to share process ids) in which case we will return an arbitrary
- * package name. It's also possible (though very unlikely) for us to be unable to work out what
- * your calling package is, in which case we will return null.
- */
- /* package for test */String getCallingPackage_() {
- int caller = Binder.getCallingUid();
- if (caller == 0) {
- return null;
- }
- String[] callerPackages = context().getPackageManager().getPackagesForUid(caller);
- if (callerPackages == null || callerPackages.length == 0) {
- return null;
- }
- if (callerPackages.length == 1) {
- return callerPackages[0];
- }
- // If we have more than one caller package, which is very unlikely, let's return the one
- // with the highest permissions. If more than one has the same permission, we don't care
- // which one we return.
- String bestSoFar = callerPackages[0];
- for (String callerPackage : callerPackages) {
- if (mVoicemailPermissions.packageHasWriteAccess(callerPackage)) {
- // Full always wins, we can return early.
- return callerPackage;
- }
- if (mVoicemailPermissions.packageHasOwnVoicemailAccess(callerPackage)) {
- bestSoFar = callerPackage;
- }
- }
- return bestSoFar;
+ @VisibleForTesting
+ String getInjectedCallingPackage() {
+ return super.getCallingPackage();
}
/**
@@ -408,7 +456,7 @@ public class VoicemailContentProvider extends ContentProvider
if (hasReadWritePermission(isQuery)) {
return null;
}
- return getEqualityClause(Voicemails.SOURCE_PACKAGE, getCallingPackage_());
+ return getEqualityClause(Voicemails.SOURCE_PACKAGE, getInjectedCallingPackage());
}
/**
@@ -424,4 +472,50 @@ public class VoicemailContentProvider extends ContentProvider
return read ? mVoicemailPermissions.callerHasReadAccess(getCallingPackage()) :
mVoicemailPermissions.callerHasWriteAccess(getCallingPackage());
}
+
+ /** Remove all records from a given source package. */
+ public void removeBySourcePackage(String packageName) {
+ delete(Voicemails.buildSourceUri(packageName), null, null);
+ delete(Status.buildSourceUri(packageName), null, null);
+ }
+
+ @VisibleForTesting
+ void performBackgroundTask(int task, Object arg) {
+ switch (task) {
+ case BACKGROUND_TASK_SCAN_STALE_PACKAGES:
+ removeStalePackages();
+ break;
+ }
+ }
+
+ /**
+ * Remove all records made by packages that no longer exist.
+ */
+ private void removeStalePackages() {
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "scanStalePackages start");
+ }
+
+ // Make sure all source tables still exists.
+
+ // First, list all source packages.
+ final ArraySet<String> packages = mVoicemailContentTable.getSourcePackages();
+ packages.addAll(mVoicemailStatusTable.getSourcePackages());
+
+ // Remove the ones that still exist.
+ for (int i = packages.size() - 1; i >= 0; i--) {
+ final String pkg = packages.valueAt(i);
+ final boolean installed = PackageUtils.isPackageInstalled(getContext(), pkg);
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, " " + pkg + (installed ? " installed" : " removed"));
+ }
+ if (!installed) {
+ removeBySourcePackage(pkg);
+ }
+ }
+
+ if (VERBOSE_LOGGING) {
+ Log.v(TAG, "scanStalePackages finish");
+ }
+ }
}
diff --git a/src/com/android/providers/contacts/VoicemailContentTable.java b/src/com/android/providers/contacts/VoicemailContentTable.java
index 75f95741..09a8c1f0 100644
--- a/src/com/android/providers/contacts/VoicemailContentTable.java
+++ b/src/com/android/providers/contacts/VoicemailContentTable.java
@@ -31,11 +31,15 @@ import android.os.ParcelFileDescriptor;
import android.provider.CallLog.Calls;
import android.provider.OpenableColumns;
import android.provider.VoicemailContract.Voicemails;
+import android.util.ArraySet;
import android.util.Log;
+
import com.android.common.content.ProjectionMap;
import com.android.providers.contacts.VoicemailContentProvider.UriData;
import com.android.providers.contacts.util.CloseUtils;
+
import com.google.common.collect.ImmutableSet;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -60,6 +64,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
.add(Voicemails.DURATION)
.add(Voicemails.IS_READ)
.add(Voicemails.TRANSCRIPTION)
+ .add(Voicemails.TRANSCRIPTION_STATE)
.add(Voicemails.STATE)
.add(Voicemails.SOURCE_DATA)
.add(Voicemails.SOURCE_PACKAGE)
@@ -98,6 +103,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
.add(Voicemails.DURATION)
.add(Voicemails.IS_READ)
.add(Voicemails.TRANSCRIPTION)
+ .add(Voicemails.TRANSCRIPTION_STATE)
.add(Voicemails.STATE)
.add(Voicemails.SOURCE_DATA)
.add(Voicemails.SOURCE_PACKAGE)
@@ -282,6 +288,11 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
return mDelegateHelper.openDataFile(uriData, mode);
}
+ @Override
+ public ArraySet<String> getSourcePackages() {
+ return mDbHelper.selectDistinctColumn(mTableName, Voicemails.SOURCE_PACKAGE);
+ }
+
/** Creates a clause to restrict the selection to only voicemail call type.*/
private String getCallTypeClause() {
return getEqualityClause(Calls.TYPE, Calls.VOICEMAIL_TYPE);
diff --git a/src/com/android/providers/contacts/VoicemailStatusTable.java b/src/com/android/providers/contacts/VoicemailStatusTable.java
index 52da2927..f3008c0e 100644
--- a/src/com/android/providers/contacts/VoicemailStatusTable.java
+++ b/src/com/android/providers/contacts/VoicemailStatusTable.java
@@ -27,9 +27,13 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.VoicemailContract.Status;
+import android.util.ArraySet;
+
import com.android.common.content.ProjectionMap;
import com.android.providers.contacts.VoicemailContentProvider.UriData;
+import java.util.Set;
+
/**
* Implementation of {@link VoicemailTable.Delegate} for the voicemail status table.
*
@@ -153,4 +157,9 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
private DatabaseModifier getDatabaseModifier(SQLiteDatabase db) {
return new DbModifierWithNotification(mTableName, db, mContext);
}
+
+ @Override
+ public ArraySet<String> getSourcePackages() {
+ return mDbHelper.selectDistinctColumn(mTableName, Status.SOURCE_PACKAGE);
+ }
}
diff --git a/src/com/android/providers/contacts/VoicemailTable.java b/src/com/android/providers/contacts/VoicemailTable.java
index 9e6c4312..fcb653ce 100644
--- a/src/com/android/providers/contacts/VoicemailTable.java
+++ b/src/com/android/providers/contacts/VoicemailTable.java
@@ -20,6 +20,7 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
+import android.util.ArraySet;
import com.android.providers.contacts.VoicemailContentProvider.UriData;
@@ -44,6 +45,7 @@ public interface VoicemailTable {
public String getType(UriData uriData);
public ParcelFileDescriptor openFile(UriData uriData, String mode)
throws FileNotFoundException;
+ public ArraySet<String> getSourcePackages();
}
/**
diff --git a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
index 1501138c..20e3bbec 100644
--- a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
@@ -69,6 +69,7 @@ import android.provider.ContactsContract.StatusUpdates;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
+import android.util.Slog;
import java.util.ArrayList;
import java.util.Collections;
@@ -917,6 +918,10 @@ public abstract class AbstractContactAggregator {
* Updates the contact ID for the specified contact and marks the raw contact as aggregated.
*/
private void setContactIdAndMarkAggregated(long rawContactId, long contactId) {
+ if (contactId == 0) {
+ // Use Slog instead of Log, to prevent the process from crashing.
+ Slog.wtfStack(TAG, "Detected contact-id 0");
+ }
mContactIdAndMarkAggregatedUpdate.bindLong(1, contactId);
mContactIdAndMarkAggregatedUpdate.bindLong(2, rawContactId);
mContactIdAndMarkAggregatedUpdate.execute();
@@ -1263,8 +1268,8 @@ public abstract class AbstractContactAggregator {
+ 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 +1304,8 @@ public abstract class AbstractContactAggregator {
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 +1324,8 @@ public abstract class AbstractContactAggregator {
+ 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 +1340,8 @@ public abstract class AbstractContactAggregator {
+ 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 +1355,8 @@ public abstract class AbstractContactAggregator {
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 +1444,12 @@ public abstract class AbstractContactAggregator {
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 +1528,9 @@ public abstract class AbstractContactAggregator {
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 00000000..facd02e2
--- /dev/null
+++ b/src/com/android/providers/contacts/sqlite/DatabaseAnalyzer.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to extract table/view/column names from databases.
+ */
+@VisibleForTesting
+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.
+ */
+ @VisibleForTesting
+ 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 00000000..2db1f479
--- /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 d719313a..23c144ac 100644
--- a/src/com/android/providers/contacts/util/DbQueryUtils.java
+++ b/src/com/android/providers/contacts/util/DbQueryUtils.java
@@ -125,7 +125,7 @@ public class DbQueryUtils {
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/src/com/android/providers/contacts/util/PackageUtils.java b/src/com/android/providers/contacts/util/PackageUtils.java
new file mode 100644
index 00000000..f4840458
--- /dev/null
+++ b/src/com/android/providers/contacts/util/PackageUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.contacts.util;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+public class PackageUtils {
+ private PackageUtils() {
+ }
+
+ /**
+ * @return TRUE if the given package is installed for this user.
+ */
+ public static boolean isPackageInstalled(Context context, String packageName) {
+ try {
+ // Need to pass MATCH_UNINSTALLED_PACKAGES to fetch it even if the package is
+ // being updated. Then use FLAG_INSTALLED to see if it's actually installed for this
+ // user.
+ final ApplicationInfo ai = context.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ return (ai != null) && ((ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/test_common/Android.mk b/test_common/Android.mk
new file mode 100644
index 00000000..2965f8a0
--- /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
index cb78a1b6..cb78a1b6 100644
--- a/tests/src/com/android/providers/contacts/testutil/CommonDatabaseUtils.java
+++ b/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
index 442c5e71..442c5e71 100644
--- a/tests/src/com/android/providers/contacts/testutil/ContactUtil.java
+++ b/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
index 2afd5674..32e76197 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.net.Uri;
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
index 2af98299..2af98299 100644
--- a/tests/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
+++ b/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
index 2dab7f90..2dab7f90 100644
--- a/tests/src/com/android/providers/contacts/testutil/DeletedContactUtil.java
+++ b/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
index f24875b7..cef6d6df 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.content.ContentValues;
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;
@@ -49,18 +47,6 @@ public class RawContactUtil {
return CommonDatabaseUtils.singleRecordToArray(cursor);
}
- /**
- * Returns a list of raw contact records.
- *
- * @return A list of records. Where each record is represented as an array of strings.
- */
- public static List<String[]> queryByContactId(ContentResolver resolver, long contactId,
- String[] projection) {
- Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, contactId);
- Cursor cursor = resolver.query(uri, projection, null, null, null);
- return CommonDatabaseUtils.multiRecordToArray(cursor);
- }
-
public static void delete(ContentResolver resolver, long rawContactId,
boolean isSyncAdapter) {
Uri uri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId)
@@ -98,11 +84,11 @@ public class RawContactUtil {
}
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
index 05ff61d1..6c8c689a 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.accounts.Account;
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 class TestUtil {
}
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 f2e0fc1a..8df1d6d4 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -4,7 +4,11 @@ include $(CLEAR_VARS)
# 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
@@ -12,6 +16,7 @@ LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := ContactsProviderTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_INSTRUMENTATION_FOR := ContactsProvider
LOCAL_CERTIFICATE := shared
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
new file mode 100644
index 00000000..46e97bc4
--- /dev/null
+++ b/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Contacts Provider Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="ContactsProviderTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="ContactsProviderTests" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.providers.contacts.tests" />
+ <option name="runner" value="android.test.InstrumentationTestRunner" />
+ </test>
+</configuration>
diff --git a/tests/assets/upgradeTest/pre_upgrade1201.sql b/tests/assets/upgradeTest/pre_upgrade1201.sql
new file mode 100644
index 00000000..6a284ead
--- /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 09241549..74642cb7 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -18,6 +18,7 @@ package com.android.providers.contacts;
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;
@@ -127,7 +128,8 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
protected void setUp() throws Exception {
super.setUp();
- mActor = new ContactsActor(getContext(), PACKAGE_GREY, getProviderClass(), getAuthority());
+ mActor = new ContactsActor(
+ getContext(), getContextPackageName(), getProviderClass(), getAuthority());
mResolver = mActor.resolver;
if (mActor.provider instanceof SynchronousContactsProvider2) {
getContactsProvider().wipeData();
@@ -142,8 +144,13 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
"android.permission.WRITE_SOCIAL_STREAM");
}
+ protected String getContextPackageName() {
+ return PACKAGE_GREY;
+ }
+
@Override
protected void tearDown() throws Exception {
+ mActor.shutdown();
sMockClock.uninstall();
super.tearDown();
}
@@ -1095,6 +1102,9 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
assertCursorValues(c, values);
} catch (Error e) {
TestUtils.dumpCursor(c);
+
+ // Dump with no selection.
+ TestUtils.dumpUri(mResolver, uri);
throw e;
} finally {
c.close();
@@ -1158,7 +1168,7 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
}
}
- 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 +1198,7 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
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 +1239,7 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
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 +1383,17 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase {
.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 73303d00..0eb12ad8 100644
--- a/tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java
@@ -29,7 +29,7 @@ import java.util.HashMap;
* 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 @@ public class BaseDatabaseHelperUpgradeTest extends AndroidTestCase {
@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 @@ public class BaseDatabaseHelperUpgradeTest extends AndroidTestCase {
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/BaseVoicemailProviderTest.java b/tests/src/com/android/providers/contacts/BaseVoicemailProviderTest.java
index e70492d3..1020da89 100644
--- a/tests/src/com/android/providers/contacts/BaseVoicemailProviderTest.java
+++ b/tests/src/com/android/providers/contacts/BaseVoicemailProviderTest.java
@@ -43,7 +43,6 @@ public abstract class BaseVoicemailProviderTest extends BaseContactsProvider2Tes
@Override
protected void setUp() throws Exception {
super.setUp();
- addProvider(TestVoicemailProvider.class, VoicemailContract.AUTHORITY);
TestVoicemailProvider.setVvmProviderCallDelegate(createMockProviderCalls());
mPackageManager = (ContactsMockPackageManager) getProvider()
@@ -165,6 +164,12 @@ public abstract class BaseVoicemailProviderTest extends BaseContactsProvider2Tes
mDelegate = delegate;
}
+ // Run the tasks synchronously.
+ @Override
+ void scheduleTask(int taskId, Object arg) {
+ performBackgroundTask(taskId, arg);
+ }
+
@Override
protected CallLogDatabaseHelper getDatabaseHelper(Context context) {
return new CallLogDatabaseHelperTestable(context, /* contactsDbForMigration = */ null);
@@ -189,7 +194,7 @@ public abstract class BaseVoicemailProviderTest extends BaseContactsProvider2Tes
}
@Override
- protected String getCallingPackage_() {
+ protected String getInjectedCallingPackage() {
return getContext().getPackageName();
}
diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
index 8ee7a5be..f6078464 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -61,7 +61,7 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
Voicemails.DIRTY,
Voicemails.DELETED};
/** Total number of columns exposed by call_log provider. */
- private static final int NUM_CALLLOG_FIELDS = 30;
+ private static final int NUM_CALLLOG_FIELDS = 31;
private CallLogProviderTestable mCallLogProvider;
diff --git a/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
index 8ad5ca17..b15d01ae 100644
--- a/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
+++ b/tests/src/com/android/providers/contacts/ContactDirectoryManagerTest.java
@@ -16,6 +16,8 @@
package com.android.providers.contacts;
+import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
+
import android.accounts.Account;
import android.content.ContentValues;
import android.content.Context;
@@ -120,6 +122,13 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
.getContext().getPackageManager();
}
+ @Override
+ protected String getContextPackageName() {
+ // In this test, we need to use the real package name, because that'll be recorded in the
+ // directory table, and if it's wrong, the tests would get confused.
+ return getContext().getPackageName();
+ }
+
public void testIsDirectoryProvider() {
ProviderInfo provider = new ProviderInfo();
@@ -172,7 +181,7 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
Directory.PHOTO_SUPPORT_FULL);
- mDirectoryManager.scanAllPackages();
+ assertEquals(3, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null,
/* order by=*/ Directory.DIRECTORY_AUTHORITY + "," + Directory.ACCOUNT_NAME +
@@ -197,18 +206,51 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
assertTrue(cursor.moveToPosition(3));
- assertDirectoryRow(cursor, "contactsTestPackage", "com.android.contacts", null, null,
+ assertDirectoryRow(cursor, getContext().getPackageName(),
+ "com.android.contacts", null, null,
null, -1 /* =any */, Directory.EXPORT_SUPPORT_NONE,
Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
assertTrue(cursor.moveToPosition(4));
- assertDirectoryRow(cursor, "contactsTestPackage", "com.android.contacts", null, null,
+ assertDirectoryRow(cursor, getContext().getPackageName(),
+ "com.android.contacts", null, null,
null, -1 /* =any */, Directory.EXPORT_SUPPORT_NONE,
Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
cursor.close();
}
+ public void testScanAllProviders_scanCondition() throws Exception {
+ testScanAllProviders();
+
+ // Nothing has changed, so no scanning.
+ assertEquals(0, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
+
+ // rescan = true, so a full-scan should happen.
+ assertEquals(3, mDirectoryManager.scanAllPackages(/* rescan=*/ true));
+
+ // Change GAL packages, a scan should happen.
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(
+ createProviderPackage("test.package2", "authority2"),
+ createPackage("test.packageX", "authorityX", false)));
+ assertEquals(1, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
+
+ // Remove the non-GAL package, no scan should happen.
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(
+ createProviderPackage("test.package2", "authority2")));
+ assertEquals(0, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
+
+ // Remove GAL package 2 and add 1, a scan should happen.
+ mPackageManager.setInstalledPackages(
+ Lists.newArrayList(
+ createProviderPackage("test.package1", "authority1"),
+ createPackage("test.packageX", "authorityX", false)));
+ assertEquals(2, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
+
+ }
+
public void testPackageInstalled() throws Exception {
mPackageManager.setInstalledPackages(
Lists.newArrayList(createProviderPackage("test.package1", "authority1"),
@@ -222,7 +264,7 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
Directory.PHOTO_SUPPORT_FULL);
- mDirectoryManager.scanAllPackages();
+ mDirectoryManager.scanAllPackages(/* rescan=*/ false);
// At this point the manager has discovered a single directory (plus two
// standard ones).
@@ -283,7 +325,7 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
Directory.PHOTO_SUPPORT_FULL);
- mDirectoryManager.scanAllPackages();
+ mDirectoryManager.scanAllPackages(/* rescan=*/ false);
// At this point the manager has discovered two custom directories.
Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
@@ -329,7 +371,7 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
Directory.PHOTO_SUPPORT_FULL);
- mDirectoryManager.scanAllPackages();
+ mDirectoryManager.scanAllPackages(/* rescan=*/ false);
// At this point the manager has discovered two custom directories.
Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
@@ -387,7 +429,7 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
Directory.PHOTO_SUPPORT_FULL);
- mDirectoryManager.scanAllPackages();
+ mDirectoryManager.scanAllPackages(/* rescan=*/ false);
// At this point the manager has discovered two custom directories.
Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
@@ -451,7 +493,7 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY,
Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY);
- mDirectoryManager.scanAllPackages();
+ mDirectoryManager.scanAllPackages(/* rescan=*/ false);
accounts = new Account[]{new Account("account-name1", "account-type1")};
mActor.setAccounts(accounts);
@@ -481,7 +523,7 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
Directory.PHOTO_SUPPORT_NONE);
- mDirectoryManager.scanAllPackages();
+ mDirectoryManager.scanAllPackages(/* rescan=*/ false);
// Pretend to replace the package with a different provider inside
MatrixCursor response2 = provider1.createResponseCursor();
@@ -515,7 +557,7 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
Directory.PHOTO_SUPPORT_NONE);
- mDirectoryManager.scanAllPackages();
+ mDirectoryManager.scanAllPackages(/* rescan=*/ false);
Cursor cursor = mResolver.query(
Directory.CONTENT_URI, new String[] { Directory._ID }, null, null, null);
@@ -555,7 +597,7 @@ public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
Directory.PHOTO_SUPPORT_NONE);
- mDirectoryManager.scanAllPackages();
+ mDirectoryManager.scanAllPackages(/* rescan=*/ false);
Cursor cursor = mResolver.query(
Directory.CONTENT_URI, new String[] { Directory._ID }, null, null, null);
diff --git a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
index 8c2a754f..3d8b8eb3 100644
--- a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
+++ b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
@@ -93,7 +93,6 @@ public class ContactMetadataProviderTest extends BaseContactsProvider2Test {
"' AND " + MetadataSync.DATA_SET + "='" + TEST_DATA_SET2 + "'";
private ContactMetadataProvider mContactMetadataProvider;
- private AccountWithDataSet mTestAccount;
private ContentValues defaultValues;
@Override
@@ -104,7 +103,7 @@ public class ContactMetadataProviderTest extends BaseContactsProvider2Test {
// 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 @@ public class ContactMetadataProviderTest extends BaseContactsProvider2Test {
// 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 @@ public class ContactMetadataProviderTest extends BaseContactsProvider2Test {
}
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 e3561073..11a13c27 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.AccountManagerFuture;
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.location.CountryListener;
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.Data;
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 @@ public class ContactsActor {
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 class ContactsActor {
public void sendBroadcast(Intent intent, String receiverPermission) {
// Ignore.
}
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
};
mMockAccountManager = new MockAccountManager(mProviderContext);
@@ -378,7 +381,11 @@ public class ContactsActor {
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 @@ public class ContactsActor {
resolver.addProvider(a, provider);
resolver.addProvider("0@" + a, provider);
}
+ mAllProviders.add(provider);
return provider;
}
@@ -450,7 +458,7 @@ public class ContactsActor {
mGrantedPermissions = grantedPermissions;
mGrantedUriPermissions = grantedUriPermissions;
- mPackageManager = new ContactsMockPackageManager();
+ mPackageManager = new ContactsMockPackageManager(overallContext);
mPackageManager.addPackage(1000, PACKAGE_GREY);
mPackageManager.addPackage(2000, PACKAGE_RED);
mPackageManager.addPackage(3000, PACKAGE_GREEN);
@@ -734,4 +742,10 @@ public class ContactsActor {
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 ff6a7a2c..8cf9ea42 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 com.google.android.collect.Sets;
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 @@ public class ContactsDatabaseHelperTest extends BaseContactsProvider2Test {
}
/**
- * 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 @@ public class ContactsDatabaseHelperTest extends BaseContactsProvider2Test {
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 @@ public class ContactsDatabaseHelperTest extends BaseContactsProvider2Test {
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) i, 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 a6077119..185fa031 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.provider.VoicemailContract.Voicemails;
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.NicknameLookupColum
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 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
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 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
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 class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
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 class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
*/
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 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 void upgradeTo1110() {
- mHelper.onUpgrade(mDb, 1109, 1110);
- // TODO: Test this upgrade.
+ private int upgrade(int upgradeFrom, int upgradeTo) {
+ if (upgradeFrom < upgradeTo) {
+ mHelper.onUpgrade(mDb, upgradeFrom, upgradeTo);
+ }
+ return upgradeTo;
}
/**
@@ -138,15 +218,7 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
* 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 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
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 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
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 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
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 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
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 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
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/ContactsMockPackageManager.java b/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
index 1f3f52e3..a9420dda 100644
--- a/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
+++ b/tests/src/com/android/providers/contacts/ContactsMockPackageManager.java
@@ -20,6 +20,7 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Binder;
@@ -36,11 +37,15 @@ import java.util.List;
* {@link Context#getPackageName()}.
*/
public class ContactsMockPackageManager extends MockPackageManager {
+
+ private final Context mRealContext;
+
private final HashMap<Integer, String> mForward = new HashMap<Integer, String>();
private final HashMap<String, Integer> mReverse = new HashMap<String, Integer>();
private List<PackageInfo> mPackages;
- public ContactsMockPackageManager() {
+ public ContactsMockPackageManager(Context realContext) {
+ mRealContext = realContext;
}
/**
@@ -76,10 +81,15 @@ public class ContactsMockPackageManager extends MockPackageManager {
}
@Override
- public ApplicationInfo getApplicationInfo(String packageName, int flags) {
+ public ApplicationInfo getApplicationInfo(String packageName, int flags)
+ throws NameNotFoundException {
ApplicationInfo info = new ApplicationInfo();
Integer uid = mReverse.get(packageName);
+ if (uid == null) {
+ throw new NameNotFoundException();
+ }
info.uid = (uid != null) ? uid : -1;
+ info.flags = ApplicationInfo.FLAG_INSTALLED;
return info;
}
@@ -109,6 +119,34 @@ public class ContactsMockPackageManager extends MockPackageManager {
@Override
public Resources getResourcesForApplication(String appPackageName) {
+ if (mRealContext.getPackageName().equals(appPackageName)) {
+ return mRealContext.getResources();
+ }
return new ContactsMockResources();
}
+
+ @Override
+ public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags,
+ String metaDataKey) {
+ final List<ProviderInfo> ret = new ArrayList<>();
+ final List<PackageInfo> packages = getInstalledPackages(flags);
+ if (packages == null) {
+ return ret;
+ }
+ for (PackageInfo pkg : packages) {
+ if (pkg.providers == null) {
+ continue;
+ }
+ for (ProviderInfo proi : pkg.providers) {
+ if (metaDataKey == null) {
+ ret.add(proi);
+ } else {
+ if (proi.metaData != null && proi.metaData.containsKey(metaDataKey)) {
+ ret.add(proi);
+ }
+ }
+ }
+ }
+ return ret;
+ }
}
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index f432b0bb..e9cf2a18 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 class ContactsProvider2Test extends BaseContactsProvider2Test {
});
}
+ 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
/**
* 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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, 5,
+ 1111111 / 86400 * 86400);
// Update AggregationException table.
RawContactInfo aggregationContact = new RawContactInfo(
@@ -3034,7 +3061,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ values.put(Contacts.TIMES_CONTACTED, 4);
assertStoredValues(contactUri, values);
assertSelection(Contacts.CONTENT_URI, values, Contacts._ID, contactId);
}
@@ -3474,6 +3502,9 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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, 4);
+
assertStoredValuesWithProjection(contactUri, values);
assertSelectionWithProjection(Contacts.CONTENT_URI, values, Contacts._ID, contactId);
}
@@ -3496,6 +3527,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goulash");
+ values.put(Contacts.TIMES_CONTACTED, 4);
assertStoredValuesWithProjection(filterUri1, values);
assertContactFilter(contactId, "goolash");
@@ -3530,6 +3562,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goog411@acme.com");
+ values.put(Contacts.TIMES_CONTACTED, 4);
assertStoredValuesWithProjection(filterUri1, values);
assertContactFilter(contactId, "goog");
@@ -3556,6 +3589,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "18004664411");
+ values.put(Contacts.TIMES_CONTACTED, 4);
assertStoredValuesWithProjection(filterUri1, values);
assertContactFilter(contactId, "18004664411");
@@ -3587,7 +3621,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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, 2);
assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI,
new ContentValues[] { values4, values1, values3 });
@@ -3654,6 +3691,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 +3775,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 +3849,15 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2);
sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2);
+ values1.put(Contacts.TIMES_CONTACTED, 1);
+ values2.put(Contacts.TIMES_CONTACTED, 2);
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 +3918,51 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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", 2, 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 12, instead of 10 (which is the lowres of the
+ // sum).
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 12, 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", 2, 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", 15, 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", 22, 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", 32, 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 +3982,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 +4164,9 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
long nonProfileRawContactId = createBasicNonProfileContact(nonProfileValues);
long nonProfileContactId = queryContactId(nonProfileRawContactId);
+ nonProfileValues.put(Contacts.TIMES_CONTACTED, 4);
+ profileValues.put(Contacts.TIMES_CONTACTED, 4);
+
assertStoredValues(Contacts.CONTENT_URI, nonProfileValues);
assertSelection(Contacts.CONTENT_URI, nonProfileValues, Contacts._ID, nonProfileContactId);
@@ -4115,7 +4180,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// Create a non-profile contact - this should be returned.
ContentValues nonProfileValues = new ContentValues();
createBasicNonProfileContact(nonProfileValues);
-
+ nonProfileValues.put(Contacts.TIMES_CONTACTED, 4);
assertStoredValues(Contacts.CONTENT_URI, new ContentValues[] {nonProfileValues});
}
@@ -4123,6 +4188,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
ContentValues profileValues = new ContentValues();
createBasicProfileContact(profileValues);
+ profileValues.put(Contacts.TIMES_CONTACTED, 4);
assertStoredValues(Profile.CONTENT_URI, profileValues);
}
@@ -4171,6 +4237,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// The raw contact view doesn't include the photo ID.
profileValues.remove(Contacts.PHOTO_ID);
+ profileValues.put(Contacts.TIMES_CONTACTED, 4);
assertStoredValues(Profile.CONTENT_RAW_CONTACTS_URI, profileValues);
}
@@ -4180,6 +4247,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// The raw contact view doesn't include the photo ID.
profileValues.remove(Contacts.PHOTO_ID);
+ profileValues.put(Contacts.TIMES_CONTACTED, 4);
assertStoredValues(ContentUris.withAppendedId(
Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId), profileValues);
}
@@ -5225,18 +5293,29 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
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 +5373,30 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 +6872,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 +6983,41 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
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 +7025,10 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 +8785,22 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
}
}
- 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 +8849,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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 +8857,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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,55 +8865,54 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// 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
)
);
@@ -8808,15 +8920,15 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertStoredValuesWithProjection(RawContacts.CONTENT_URI,
cv(RawContacts._ID, rid1,
RawContacts.TIMES_CONTACTED, 2,
- RawContacts.LAST_TIME_CONTACTED, startTime + 1
+ RawContacts.LAST_TIME_CONTACTED, (startTime + 86400) / 86400 * 86400
),
cv(RawContacts._ID, rid2,
RawContacts.TIMES_CONTACTED, 3,
- RawContacts.LAST_TIME_CONTACTED, startTime + 3
+ RawContacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(RawContacts._ID, rid3,
RawContacts.TIMES_CONTACTED, 2,
- RawContacts.LAST_TIME_CONTACTED, startTime + 3
+ RawContacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(RawContacts._ID, rid4,
RawContacts.TIMES_CONTACTED, 0,
@@ -8832,15 +8944,15 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertStoredValuesWithProjection(Contacts.CONTENT_URI,
cv(Contacts._ID, cid1,
Contacts.TIMES_CONTACTED, 4,
- Contacts.LAST_TIME_CONTACTED, startTime + 3
+ Contacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(Contacts._ID, cid3,
Contacts.TIMES_CONTACTED, 2,
- Contacts.LAST_TIME_CONTACTED, startTime + 3
+ 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
)
);
@@ -8883,26 +8995,45 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
String[] projection = new String[]{ContactsContract.RawContacts.DIRTY,
ContactsContract.RawContacts.DELETED};
- List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids.mContactId,
+ String[] record = RawContactUtil.queryByRawContactId(mResolver, ids.mRawContactId,
projection);
- for (String[] arr : records) {
- assertEquals("1", arr[0]);
- assertEquals("1", arr[1]);
- }
+ assertEquals("1", record[0]);
+ assertEquals("1", record[1]);
// Clean up
RawContactUtil.delete(mResolver, ids.mRawContactId, true);
}
- public void testContactUpdate_dirtyForMetadataChange() {
+ public void testContactDelete_checkRawContactContactId() {
+ DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
+
+ String[] projection = new String[]{ContactsContract.RawContacts.CONTACT_ID};
+ String[] record = RawContactUtil.queryByRawContactId(mResolver, ids.mRawContactId,
+ projection);
+ assertNull(record[0]);
+
+ // Clean up
+ RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+ }
+
+ 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 +9806,13 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
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 6a82bf9f..03c9e75c 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
@@ -60,7 +60,7 @@ public class ContactsProvider2TransactionTest extends BaseContactsProvider2Test
*/
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 class ContactsProvider2TransactionTest extends BaseContactsProvider2Test
*/
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 class ContactsProvider2TransactionTest extends BaseContactsProvider2Test
*/
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 class ContactsProvider2TransactionTest extends BaseContactsProvider2Test
*/
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 @@ public class ContactsProvider2TransactionTest extends BaseContactsProvider2Test
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 @@ public class ContactsProvider2TransactionTest extends BaseContactsProvider2Test
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 @@ public class ContactsProvider2TransactionTest extends BaseContactsProvider2Test
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 @@ public class ContactsProvider2TransactionTest extends BaseContactsProvider2Test
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/ContactsTaskSchedulerTest.java b/tests/src/com/android/providers/contacts/ContactsTaskSchedulerTest.java
new file mode 100644
index 00000000..df7196dc
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/ContactsTaskSchedulerTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.contacts;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+public class ContactsTaskSchedulerTest extends AndroidTestCase {
+ private static final int SHUTDOWN_SECONDS = 3;
+
+ private static class MyContactsTaskScheduler extends ContactsTaskScheduler {
+ final CountDownLatch latch;
+
+ final List<String> executed = new ArrayList<>();
+
+ public MyContactsTaskScheduler(int numExpectedTasks) {
+ super("Test", SHUTDOWN_SECONDS);
+ latch = new CountDownLatch(numExpectedTasks);
+ }
+
+ @Override
+ public void onPerformTask(int taskId, Object arg) {
+ executed.add("" + taskId + "," + arg);
+
+ latch.countDown();
+ }
+ }
+
+ public void testSimple() throws Exception {
+ final MyContactsTaskScheduler scheduler = new MyContactsTaskScheduler(3);
+
+ scheduler.scheduleTask(1);
+ scheduler.scheduleTask(10);
+ scheduler.scheduleTask(2, "arg");
+
+ assertTrue(scheduler.latch.await(10, TimeUnit.SECONDS));
+
+ assertEquals(Arrays.asList("1,null", "10,null", "2,arg"), scheduler.executed);
+
+ // Only one thread has been created.
+ assertEquals(1, scheduler.getThreadSequenceNumber());
+ }
+
+ public void testAutoShutdown() throws Exception {
+ final MyContactsTaskScheduler scheduler = new MyContactsTaskScheduler(7);
+
+ scheduler.scheduleTask(1);
+
+ // Wait for 10 seconds and the thread should shut down.
+ assertTrue(scheduler.isRunningForTest());
+ Thread.sleep(10 * 1000);
+ assertFalse(scheduler.isRunningForTest());
+
+ scheduler.scheduleTask(2);
+ assertTrue(scheduler.isRunningForTest());
+
+ Thread.sleep(1 * 1000);
+ scheduler.scheduleTask(3);
+
+ Thread.sleep(1 * 1000);
+ scheduler.scheduleTask(4);
+
+ Thread.sleep(1 * 1000);
+ scheduler.scheduleTask(5);
+
+ Thread.sleep(1 * 1000);
+ scheduler.scheduleTask(6);
+ assertTrue(scheduler.isRunningForTest()); // Should still alive.
+
+ // Wait for 10 seconds and the thread should shut down.
+ Thread.sleep(10 * 1000);
+ assertFalse(scheduler.isRunningForTest());
+
+ scheduler.scheduleTask(7);
+ assertTrue(scheduler.isRunningForTest());
+
+ assertTrue(scheduler.latch.await(10, TimeUnit.SECONDS));
+ assertEquals(7, scheduler.executed.size());
+
+ // Only one thread has been created.
+ assertEquals(3, scheduler.getThreadSequenceNumber());
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/DirectoryTest.java b/tests/src/com/android/providers/contacts/DirectoryTest.java
index 99f05ce8..a832a95b 100644
--- a/tests/src/com/android/providers/contacts/DirectoryTest.java
+++ b/tests/src/com/android/providers/contacts/DirectoryTest.java
@@ -16,6 +16,8 @@
package com.android.providers.contacts;
+import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
+
import android.accounts.Account;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -43,12 +45,16 @@ import com.android.providers.contacts.testutil.RawContactUtil;
@MediumTest
public class DirectoryTest extends BaseContactsProvider2Test {
+ protected String getContextPackageName() {
+ return getContext().getPackageName();
+ }
+
public void testDefaultDirectory() {
ContentValues values = new ContentValues();
Uri defaultDirectoryUri =
ContentUris.withAppendedId(Directory.CONTENT_URI, Directory.DEFAULT);
- values.put(Directory.PACKAGE_NAME, "contactsTestPackage");
+ values.put(Directory.PACKAGE_NAME, getContext().getPackageName());
values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
values.put(Directory.TYPE_RESOURCE_ID, R.string.default_directory);
values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
@@ -64,7 +70,7 @@ public class DirectoryTest extends BaseContactsProvider2Test {
Uri defaultDirectoryUri =
ContentUris.withAppendedId(Directory.CONTENT_URI, Directory.LOCAL_INVISIBLE);
- values.put(Directory.PACKAGE_NAME, "contactsTestPackage");
+ values.put(Directory.PACKAGE_NAME, getContext().getPackageName());
values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
values.put(Directory.TYPE_RESOURCE_ID, R.string.local_invisible_directory);
values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
diff --git a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
index 378c9eb3..21d148c9 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 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test {
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 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test {
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 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test {
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 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test {
values.clear();
putContactValuesExceptName(values);
+ values = noStats(values);
values.put(People.PRIMARY_PHONE_ID, ContentUris.parseId(phoneUri1));
assertStoredValues(phoneUri2, values);
@@ -279,6 +297,7 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test {
values.clear();
putContactValuesExceptName(values);
+ values = noStats(values);
values.put(People.PRIMARY_EMAIL_ID, ContentUris.parseId(emailUri1));
assertStoredValues(emailUri2, values);
@@ -314,9 +333,9 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test {
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 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test {
// 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 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test {
// 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 @@ public class LegacyContactsProviderTest extends BaseContactsProvider2Test {
// 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 4e797f75..8ba0438d 100644
--- a/tests/src/com/android/providers/contacts/PhotoStoreTest.java
+++ b/tests/src/com/android/providers/contacts/PhotoStoreTest.java
@@ -56,7 +56,7 @@ public class PhotoStoreTest extends PhotoLoadingTestCase {
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 00000000..260b730f
--- /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/StandaloneContactsProvider2.java b/tests/src/com/android/providers/contacts/SecondaryUserContactsProvider2.java
index 8dd09bfa..61ae1ca3 100644
--- a/tests/src/com/android/providers/contacts/StandaloneContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SecondaryUserContactsProvider2.java
@@ -18,27 +18,22 @@ package com.android.providers.contacts;
import android.content.Context;
/**
- * A subclass of {@link SynchronousContactsProvider2} that doesn't reuse the database helper.
+ * A subclass of {@link SynchronousContactsProvider2} that uses a different DB for secondary users.
*/
-public class StandaloneContactsProvider2 extends SynchronousContactsProvider2 {
- private static ContactsDatabaseHelper mDbHelper;
+public class SecondaryUserContactsProvider2 extends SynchronousContactsProvider2 {
+ private final String mDbSuffix;
+ private ContactsDatabaseHelper mDbHelper;
- public StandaloneContactsProvider2() {
- // No need to wipe data for this instance since it doesn't reuse the db helper.
- setDataWipeEnabled(false);
+ public SecondaryUserContactsProvider2(int userId) {
+ mDbSuffix = "-u" + userId;
}
@Override
- public ContactsDatabaseHelper getDatabaseHelper(final Context context) {
+ public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
if (mDbHelper == null) {
- mDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context);
+ mDbHelper = ContactsDatabaseHelper.getNewInstanceForTest(context,
+ TestUtils.getContactsDatabaseFilename(context, mDbSuffix));
}
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/SqlInjectionDetectionTest.java b/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
index e7b80a04..3dd6d57b 100644
--- a/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
+++ b/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
@@ -17,14 +17,15 @@
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;
import android.net.Uri;
import android.net.Uri.Builder;
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 +43,54 @@ import com.android.providers.contacts.testutil.RawContactUtil;
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);
+ if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
+ 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");
+ if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
+ 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 +119,46 @@ public class SqlInjectionDetectionTest extends BaseContactsProvider2Test {
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);
+ });
+ if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
+ 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);
+ });
+ if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
+ 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);
+ }
+ }
+
+ public void testBadInsert() {
+ if (ContactsDatabaseHelper.DISALLOW_SUB_QUERIES) {
+ assertThrows(IllegalArgumentException.class, () -> {
+ mResolver.insert(Data.CONTENT_URI, cv("_id/**/", 1));
+ });
+ }
+ }
}
diff --git a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
index 19878f81..f674dd5b 100644
--- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
@@ -34,7 +34,6 @@ public class SynchronousContactsProvider2 extends ContactsProvider2 {
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 @@ public class SynchronousContactsProvider2 extends ContactsProvider2 {
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 @@ public class SynchronousContactsProvider2 extends ContactsProvider2 {
return new SynchronousProfileProvider(this);
}
- public void setDataWipeEnabled(boolean flag) {
- mDataWipeEnabled = flag;
+ public ProfileDatabaseHelper getProfileDatabaseHelper() {
+ return getProfileProviderForTest().getDatabaseHelper();
}
@Override
@@ -100,12 +100,10 @@ public class SynchronousContactsProvider2 extends ContactsProvider2 {
@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 class SynchronousContactsProvider2 extends ContactsProvider2 {
}
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 class SynchronousContactsProvider2 extends ContactsProvider2 {
}
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,12 +212,12 @@ public class SynchronousContactsProvider2 extends ContactsProvider2 {
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)");
- getContactDirectoryManagerForTest().scanAllPackages();
+ getContactDirectoryManagerForTest().scanAllPackages(/* rescan= */ true);
}
// Flags to remember which transaction callback has been called for which mode.
diff --git a/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java b/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
index 308e67a5..79356a51 100644
--- a/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
+++ b/tests/src/com/android/providers/contacts/SynchronousProfileProvider.java
@@ -32,9 +32,10 @@ public class SynchronousProfileProvider extends ProfileProvider {
}
@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 b6d6a271..322e5b46 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 @@ public class TestUtils {
* 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 @@ public class TestUtils {
}
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 @@ public class TestUtils {
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/VoicemailCleanupServiceTest.java b/tests/src/com/android/providers/contacts/VoicemailCleanupServiceTest.java
deleted file mode 100644
index f8d76ee1..00000000
--- a/tests/src/com/android/providers/contacts/VoicemailCleanupServiceTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2011 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.ContentValues;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.VoicemailContract;
-import android.provider.VoicemailContract.Status;
-import android.provider.VoicemailContract.Voicemails;
-import android.test.suitebuilder.annotation.SmallTest;
-
-/**
- * Unit tests for {@link VoicemailCleanupService}.
- */
-@SmallTest
-public class VoicemailCleanupServiceTest extends BaseVoicemailProviderTest {
- private static final String TEST_PACKAGE_1 = "package1";
- private static final String TEST_PACKAGE_2 = "package2";
- // Object under test.
- private VoicemailCleanupService mCleanupService;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- setUpForFullPermission();
- mCleanupService = new VoicemailCleanupService();
- }
-
- public void testIntentHandling() {
- mCleanupService = new VoicemailCleanupService();
- insertDataForPackage(TEST_PACKAGE_1);
- insertDataForPackage(TEST_PACKAGE_2);
- checkDataExistsForPackages(TEST_PACKAGE_1, TEST_PACKAGE_2);
- // No action on PACKAGE_CHANGED.
- sendIntent(TEST_PACKAGE_1, Intent.ACTION_PACKAGE_CHANGED, null);
- checkDataExistsForPackages(TEST_PACKAGE_1, TEST_PACKAGE_2);
-
- // No action on PACKAGE_REPLACED.
- sendIntent(TEST_PACKAGE_1, Intent.ACTION_PACKAGE_REPLACED, null);
- checkDataExistsForPackages(TEST_PACKAGE_1, TEST_PACKAGE_2);
-
- // No action on PACKAGE_REMOVED with EXTRA_REPLACING = true.
- sendIntent(TEST_PACKAGE_1, Intent.ACTION_PACKAGE_REMOVED, true);
- checkDataExistsForPackages(TEST_PACKAGE_1, TEST_PACKAGE_2);
-
- // Data removed on PACKAGE_REMOVED but with no EXTRA_REPLACING.
- sendIntent(TEST_PACKAGE_1, Intent.ACTION_PACKAGE_REMOVED, null);
- checkDataDoesNotExistForPackage(TEST_PACKAGE_1);
- checkDataExistsForPackages(TEST_PACKAGE_2);
-
- // Data removed on PACKAGE_REMOVED with EXTRA_REPLACING = false.
- sendIntent(TEST_PACKAGE_2, Intent.ACTION_PACKAGE_REMOVED, false);
- checkDataDoesNotExistForPackage(TEST_PACKAGE_1);
- checkDataDoesNotExistForPackage(TEST_PACKAGE_2);
- }
-
- private void sendIntent(String sourcePackage, String action, Boolean replacingExtra) {
- Intent packageIntent = new Intent(action, Uri.parse("package:" + sourcePackage));
- if (replacingExtra != null) {
- packageIntent.putExtra(Intent.EXTRA_REPLACING, replacingExtra);
- }
- mCleanupService.handleIntentInternal(packageIntent, mResolver);
- }
-
- private void insertDataForPackage(String sourcePackage) {
- ContentValues values = new ContentValues();
- values.put(VoicemailContract.SOURCE_PACKAGE_FIELD, sourcePackage);
- mResolver.insert(Voicemails.buildSourceUri(sourcePackage), values);
- mResolver.insert(Status.buildSourceUri(sourcePackage), values);
- }
-
- void checkDataExistsForPackages(String... sourcePackages) {
- for (String sourcePackage : sourcePackages) {
- checkDataExistsForPackage(sourcePackage);
- }
- }
-
- private void checkDataExistsForPackage(String sourcePackage) {
- Cursor cursor = mResolver.query(
- Voicemails.buildSourceUri(sourcePackage), null, null, null, null);
- assertNotSame(0, cursor.getCount());
- cursor = mResolver.query(
- Status.buildSourceUri(sourcePackage), null, null, null, null);
- assertNotSame(0, cursor.getCount());
- }
-
- private void checkDataDoesNotExistForPackage(String sourcePackage) {
- Cursor cursor = mResolver.query(
- Voicemails.buildSourceUri(sourcePackage), null, null, null, null);
- assertEquals(0, cursor.getCount());
- cursor = mResolver.query(
- Status.buildSourceUri(sourcePackage), null, null, null, null);
- assertEquals(0, cursor.getCount());
- }
-}
diff --git a/tests/src/com/android/providers/contacts/VoicemailCleanupTest.java b/tests/src/com/android/providers/contacts/VoicemailCleanupTest.java
new file mode 100644
index 00000000..50ecbffc
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/VoicemailCleanupTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.contacts;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Status;
+import android.provider.VoicemailContract.Voicemails;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.providers.contacts.testutil.TestUtil;
+
+/**
+ * Tests for {@link VoicemailCleanupTest}.
+ */
+@SmallTest
+public class VoicemailCleanupTest extends BaseVoicemailProviderTest {
+ private static final String TEST_PACKAGE_1 = "package1";
+ private static final String TEST_PACKAGE_2 = "package2";
+ private static final String TEST_PACKAGE_3 = "package3";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ setUpForFullPermission();
+ }
+
+ /**
+ * Test for {@link ContactsPackageMonitor#cleanupVoicemail}.
+ */
+ public void testCleanupVoicemail() {
+ insertDataForPackage(TEST_PACKAGE_1);
+ insertDataForPackage(TEST_PACKAGE_2);
+
+ mPackageManager.addPackage(123456, TEST_PACKAGE_2);
+
+ checkDataExistsForPackage(TEST_PACKAGE_1);
+ checkDataExistsForPackage(TEST_PACKAGE_2);
+
+ ContactsPackageMonitor.cleanupVoicemail(getMockContext(), TEST_PACKAGE_1);
+
+ checkDataDoesNotExistForPackage(TEST_PACKAGE_1);
+ checkDataExistsForPackage(TEST_PACKAGE_2);
+
+ // Call for TEST_PACKAGE_2, which is still installed.
+ ContactsPackageMonitor.cleanupVoicemail(getMockContext(), TEST_PACKAGE_2);
+
+ checkDataDoesNotExistForPackage(TEST_PACKAGE_1);
+ checkDataExistsForPackage(TEST_PACKAGE_2);
+
+ // Uninstall the package and try again.
+ mPackageManager.removePackage(123456);
+ ContactsPackageMonitor.cleanupVoicemail(getMockContext(), TEST_PACKAGE_2);
+
+ checkDataDoesNotExistForPackage(TEST_PACKAGE_1);
+ checkDataDoesNotExistForPackage(TEST_PACKAGE_2);
+ }
+
+ private void insertDataForPackage(String sourcePackage) {
+ ContentValues values = new ContentValues();
+ values.put(VoicemailContract.SOURCE_PACKAGE_FIELD, sourcePackage);
+ mResolver.insert(Voicemails.buildSourceUri(sourcePackage), values);
+ mResolver.insert(Status.buildSourceUri(sourcePackage), values);
+ }
+
+ private static void assertBigger(int smaller, int actual) {
+ if (smaller >= actual) {
+ fail("Expected to be bigger than " + smaller + ", but was " + actual);
+ }
+ }
+
+ private void checkDataExistsForPackage(String sourcePackage) {
+ Cursor cursor = mResolver.query(
+ Voicemails.buildSourceUri(sourcePackage), null, null, null, null);
+ assertBigger(0, cursor.getCount());
+ cursor = mResolver.query(
+ Status.buildSourceUri(sourcePackage), null, null, null, null);
+ assertBigger(0, cursor.getCount());
+ }
+
+ private void checkDataDoesNotExistForPackage(String sourcePackage) {
+ Cursor cursor = mResolver.query(
+ Voicemails.buildSourceUri(sourcePackage), null,
+ "(ifnull(" + Voicemails.DELETED + ",0)==0)", null, null);
+ assertEquals(0, cursor.getCount());
+ cursor = mResolver.query(
+ Status.buildSourceUri(sourcePackage), null, null, null, null);
+ assertEquals(0, cursor.getCount());
+ }
+
+ public void testRemoveStalePackagesAtStartUp() {
+ insertDataForPackage(TEST_PACKAGE_1);
+ insertDataForPackage(TEST_PACKAGE_2);
+ insertDataForPackage(TEST_PACKAGE_3);
+
+ mPackageManager.addPackage(10001, TEST_PACKAGE_1);
+ mPackageManager.addPackage(10002, TEST_PACKAGE_2);
+
+ checkDataExistsForPackage(TEST_PACKAGE_1);
+ checkDataExistsForPackage(TEST_PACKAGE_2);
+ checkDataExistsForPackage(TEST_PACKAGE_3);
+
+ final VoicemailContentProvider provider =
+ (VoicemailContentProvider) mActor.provider;
+
+ // In unit tests, BG tasks are synchronous.
+ provider.scheduleScanStalePackages();
+
+ checkDataExistsForPackage(TEST_PACKAGE_1);
+ checkDataExistsForPackage(TEST_PACKAGE_2);
+ checkDataDoesNotExistForPackage(TEST_PACKAGE_3);
+
+ mPackageManager.removePackage(10001);
+
+ provider.scheduleScanStalePackages();
+
+ checkDataDoesNotExistForPackage(TEST_PACKAGE_1);
+ checkDataExistsForPackage(TEST_PACKAGE_2);
+ checkDataDoesNotExistForPackage(TEST_PACKAGE_3);
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
index 16abf2f6..4fa935fa 100644
--- a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
+++ b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
@@ -61,7 +61,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
Calls.COUNTRY_ISO
};
/** Total number of columns exposed by voicemail provider. */
- private static final int NUM_VOICEMAIL_FIELDS = 23;
+ private static final int NUM_VOICEMAIL_FIELDS = 24;
@Override
protected void setUp() throws Exception {
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 00000000..568e1e8f
--- /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 00000000..ee2b5be1
--- /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 e09e59ea..1bfcb17a 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 @@ public class DBQueryUtilsTest extends TestCase {
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 dce06c9f..03e82657 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 class MockClock extends Clock {
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 00000000..bb4443f3
--- /dev/null
+++ b/tests2/Android.mk
@@ -0,0 +1,40 @@
+#
+# 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_COMPATIBILITY_SUITE := device-tests
+
+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 00000000..7678bd2f
--- /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/AndroidTest.xml b/tests2/AndroidTest.xml
new file mode 100644
index 00000000..9f541e5d
--- /dev/null
+++ b/tests2/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Contacts Provider Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="ContactsProviderTests2.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="ContactsProviderTests2" />
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.providers.contacts.tests2" />
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
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 00000000..2aa6a619
--- /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();
+ }
+}
+
+
diff --git a/tools/contacts-db-schema.sh b/tools/contacts-db-schema.sh
index 3aa164bc..c5778441 100755
--- a/tools/contacts-db-schema.sh
+++ b/tools/contacts-db-schema.sh
@@ -23,7 +23,7 @@ db=/data/data/com.android.providers.contacts/databases/contacts2.db
# Otherwise sqlite3 would create an empty file owned by root.
# Sed inserts a newline after each ( and ,
-adb shell "(ls $db >/dev/null)&& sqlite3 $db \"select name, sql from sqlite_master where type in('table','index') order by name\"" |
+adb shell "(ls $db >/dev/null)&& sqlite3 $db \"select name, sql from sqlite_master where type in('table','index', 'view') order by name\"" |
sed -e 's/\([(,]\)/\1\n /g'
echo "> sqlite_stat1"
adb shell "(ls $db >/dev/null)&& sqlite3 $db \"select * from sqlite_stat1 order by tbl, idx, stat\""