aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2018-08-07 16:51:25 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2018-08-07 16:51:25 +0000
commit0f4e32be2798973a591b58c6160d3d03f8e0715c (patch)
treea8a39c799b3d67ba41cd51ebdcbdbf828515d024
parentfb691a33d15ddeb7a427c48255fb287a99c1d29e (diff)
parent5768f3e4ad48eccc658f2d6f605ad1478667ff08 (diff)
downloadContactsProvider-0f4e32be2798973a591b58c6160d3d03f8e0715c.tar.gz
-rw-r--r--AndroidManifest.xml7
-rw-r--r--res/values-bs/strings.xml2
-rw-r--r--res/values-cs/strings.xml2
-rw-r--r--res/values-da/strings.xml2
-rw-r--r--res/values-en-rCA/strings.xml35
-rw-r--r--res/values-en-rXC/strings.xml35
-rw-r--r--res/values-fa/strings.xml2
-rw-r--r--res/values-mr/strings.xml2
-rw-r--r--res/values-zh-rTW/strings.xml2
-rw-r--r--src/com/android/providers/contacts/AbstractContactsProvider.java117
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java122
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java24
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java14
-rw-r--r--src/com/android/providers/contacts/DatabaseModifier.java6
-rw-r--r--src/com/android/providers/contacts/DbModifierWithNotification.java197
-rw-r--r--src/com/android/providers/contacts/ProfileProvider.java6
-rw-r--r--src/com/android/providers/contacts/SearchIndexManager.java24
-rw-r--r--src/com/android/providers/contacts/VoicemailContentProvider.java43
-rw-r--r--src/com/android/providers/contacts/VoicemailContentTable.java45
-rw-r--r--src/com/android/providers/contacts/VoicemailNotifier.java128
-rw-r--r--src/com/android/providers/contacts/VoicemailPermissions.java35
-rw-r--r--src/com/android/providers/contacts/VoicemailStatusTable.java24
-rw-r--r--src/com/android/providers/contacts/VoicemailTable.java2
-rw-r--r--src/com/android/providers/contacts/util/CappedStringBuilder.java69
-rw-r--r--tests/Android.mk5
-rw-r--r--tests/AndroidTest.xml1
-rw-r--r--tests/src/com/android/providers/contacts/BaseVoicemailProviderTest.java11
-rw-r--r--tests/src/com/android/providers/contacts/ContactsActor.java44
-rw-r--r--tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java11
-rw-r--r--tests/src/com/android/providers/contacts/ContactsProvider2Test.java42
-rw-r--r--tests/src/com/android/providers/contacts/VoicemailProviderTest.java263
-rw-r--r--tests/src/com/android/providers/contacts/util/CappedStringBuilderTest.java84
-rw-r--r--tests2/Android.mk5
-rw-r--r--tests2/AndroidTest.xml1
34 files changed, 1095 insertions, 317 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index aa1c396f..559e60f1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.SEND_CALL_LOG_CHANGE" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
<permission
android:name="android.permission.SEND_CALL_LOG_CHANGE"
@@ -65,11 +66,13 @@
android:writePermission="android.permission.MANAGE_USERS">
</provider>
+ <!-- Note: While this provider does not declare a permission explicitly, it enforces that
+ the caller has either ADD_VOICEMAIL or carrier privileges at a minimum to access it.
+ Additional permission checks may be done depending on the operation. -->
<provider android:name="VoicemailContentProvider"
android:authorities="com.android.voicemail"
android:syncable="false" android:multiprocess="false"
- android:exported="true"
- android:permission="com.android.voicemail.permission.ADD_VOICEMAIL">
+ android:exported="true">
</provider>
<provider android:name="ContactMetadataProvider"
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 37bc1502..e1fae510 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -26,7 +26,7 @@
<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_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 e-poruku."</string>
<string name="debug_dump_delete_button" msgid="7832879421132026435">"Izbriši sada"</string>
<string name="debug_dump_start_button" msgid="2837506913757600001">"Započni"</string>
<string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Odaberite program za slanje fajla"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 489b5b7f..47fc9312 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,7 +27,7 @@
<string name="voicemail_from_column" msgid="435732568832121444">"Hlasová zpráva od uživatele "</string>
<string name="debug_dump_title" msgid="4916885724165570279">"Kopírování databáze kontaktů"</string>
<string name="debug_dump_database_message" msgid="406438635002392290">"Chystáte se 1) vytvořit v interním úložišti kopii databáze obsahující všechny informace o kontaktech a veškerou historii hovorů a 2) odeslat ji e-mailem. Po úspěšném zkopírování ze zařízení nebo přijetí e-mailem ji nezapomeňte ihned odstranit."</string>
- <string name="debug_dump_delete_button" msgid="7832879421132026435">"Smazat nyní"</string>
+ <string name="debug_dump_delete_button" msgid="7832879421132026435">"Smazat teď"</string>
<string name="debug_dump_start_button" msgid="2837506913757600001">"Spustit"</string>
<string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Vyberte program pro odeslání souboru."</string>
<string name="debug_dump_email_subject" msgid="108188398416385976">"Databáze kontaktů v příloze"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index fde3a2f2..c77b4c54 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -26,7 +26,7 @@
<string name="local_invisible_directory" msgid="705244318477396120">"Andre"</string>
<string name="voicemail_from_column" msgid="435732568832121444">"Telefonsvarerbesked fra "</string>
<string name="debug_dump_title" msgid="4916885724165570279">"Kopiér database med kontaktpersoner"</string>
- <string name="debug_dump_database_message" msgid="406438635002392290">"Du er ved at 1) lave en kopi af din database, som indeholder alle oplysninger om dine kontaktpersoner og alle opkaldslister, til det interne lager, og 2) sende den som e-mail. Husk at slette kopien, så snart du har kopieret den fra enheden, eller e-mailen er modtaget."</string>
+ <string name="debug_dump_database_message" msgid="406438635002392290">"Du er ved at 1) lave en kopi af din database, som indeholder alle oplysninger om dine kontaktpersoner og alle opkaldslister, til det interne lager, og 2) sende den som mail. Husk at slette kopien, så snart du har kopieret den fra enheden, eller mailen er modtaget."</string>
<string name="debug_dump_delete_button" msgid="7832879421132026435">"Slet nu"</string>
<string name="debug_dump_start_button" msgid="2837506913757600001">"Start"</string>
<string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Vælg et program, for at sende din fil"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
new file mode 100644
index 00000000..c62b699a
--- /dev/null
+++ b/res/values-en-rCA/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 Core Apps"</string>
+ <string name="app_label" msgid="3389954322874982620">"Contacts Storage"</string>
+ <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="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>
+ <string name="debug_dump_title" msgid="4916885724165570279">"Copy contacts database"</string>
+ <string name="debug_dump_database_message" msgid="406438635002392290">"You are about to 1) make a copy of your database which includes all contacts related information and all call log to the internal storage, and 2) email it. Remember to delete the copy as soon as you have successfully copied it off the device or the email is received."</string>
+ <string name="debug_dump_delete_button" msgid="7832879421132026435">"Delete now"</string>
+ <string name="debug_dump_start_button" msgid="2837506913757600001">"Start"</string>
+ <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"Choose a programme to send your file"</string>
+ <string name="debug_dump_email_subject" msgid="108188398416385976">"Contacts Db attached"</string>
+ <string name="debug_dump_email_body" msgid="4577749800871444318">"Attached is my contacts database with all my contacts information. Handle with care."</string>
+</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
new file mode 100644
index 00000000..b7681dd5
--- /dev/null
+++ b/res/values-en-rXC/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 Core Apps‎‏‎‎‏‎"</string>
+ <string name="app_label" msgid="3389954322874982620">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎Contacts Storage‎‏‎‎‏‎"</string>
+ <string name="provider_label" msgid="6012150850819899907">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‎‏‏‎Contacts‎‏‎‎‏‎"</string>
+ <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‏‎‏‎Contacts 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="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>
+ <string name="debug_dump_title" msgid="4916885724165570279">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎Copy contacts database‎‏‎‎‏‎"</string>
+ <string name="debug_dump_database_message" msgid="406438635002392290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‎You are about to 1) make a copy of your database which includes all contacts related information and all call log to the internal storage, and 2) email it. Remember to delete the copy as soon as you have successfully copied it off the device or the email is received.‎‏‎‎‏‎"</string>
+ <string name="debug_dump_delete_button" msgid="7832879421132026435">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎‎‏‏‎Delete now‎‏‎‎‏‎"</string>
+ <string name="debug_dump_start_button" msgid="2837506913757600001">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‎‎‎‏‎Start‎‏‎‎‏‎"</string>
+ <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‎Choose a program to send your file‎‏‎‎‏‎"</string>
+ <string name="debug_dump_email_subject" msgid="108188398416385976">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎Contacts Db attached‎‏‎‎‏‎"</string>
+ <string name="debug_dump_email_body" msgid="4577749800871444318">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎Attached is my contacts database with all my contacts information. Handle with care.‎‏‎‎‏‎"</string>
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 3266571b..4af29ec9 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -26,7 +26,7 @@
<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-mr/strings.xml b/res/values-mr/strings.xml
index a4f324e0..171febea 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -29,7 +29,7 @@
<string name="debug_dump_database_message" msgid="406438635002392290">"आपण 1) आपल्‍या डेटाबेसची प्रत बनवणार आहात जिच्‍यामध्‍ये सर्व संपर्कांसंबंधी माहिती आणि अंतर्गत संचयनावरील कॉल लॉग समाविष्‍ट असतात आणि 2) ती ईमेल करणार आहात. आपण डिव्‍हाइसवरून यशस्‍वीरित्‍या प्रत कॉपी केल्‍यानंतर किंवा ईमेल प्राप्त केल्‍यानंतर लगेच ती हटविण्‍याचे लक्षात ठेवा."</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">"संपर्क Db संलग्‍न केला"</string>
<string name="debug_dump_email_body" msgid="4577749800871444318">"संलग्‍न केलेला माझ्‍या सर्व संपर्क माहितीसह माझा संपर्क डेटाबेस आहे. काळजीपूर्वक हाताळणी करा."</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index f259d839..decb5b6a 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -26,7 +26,7 @@
<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_database_message" msgid="406438635002392290">"您即將要 1) 將您的資料庫 (包含所有聯絡人相關資訊及所有通話記錄) 複製到內部儲存空間,以及 2) 透過電子郵件傳送副本。提醒您,當您順利複製裝置上的資料或收到電子郵件後,請儘快刪除副本。"</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/src/com/android/providers/contacts/AbstractContactsProvider.java b/src/com/android/providers/contacts/AbstractContactsProvider.java
index ebf0a1b9..e00bd862 100644
--- a/src/com/android/providers/contacts/AbstractContactsProvider.java
+++ b/src/com/android/providers/contacts/AbstractContactsProvider.java
@@ -16,10 +16,6 @@
package com.android.providers.contacts;
-import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
@@ -38,8 +34,11 @@ import android.provider.BaseColumns;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.util.SparseLongArray;
+
+import com.android.internal.util.ProviderAccessStats;
+import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -116,23 +115,8 @@ public abstract class AbstractContactsProvider extends ContentProvider
*/
private SQLiteTransactionListener mSerializedDbTransactionListener;
- private final long mStartTime = SystemClock.elapsedRealtime();
-
- private final Object mStatsLock = new Object();
- protected final SparseBooleanArray mAllCallingUids = new SparseBooleanArray();
- protected final SparseLongArray mQueryStats = new SparseLongArray();
- protected final SparseLongArray mBatchStats = new SparseLongArray();
- protected final SparseLongArray mInsertStats = new SparseLongArray();
- protected final SparseLongArray mUpdateStats = new SparseLongArray();
- protected final SparseLongArray mDeleteStats = new SparseLongArray();
- protected final SparseLongArray mInsertInBatchStats = new SparseLongArray();
- 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);
+ protected final ProviderAccessStats mStats = new ProviderAccessStats();
@Override
public boolean onCreate() {
@@ -158,47 +142,19 @@ public abstract class AbstractContactsProvider extends ContentProvider
mSerializedDbTransactionListener = listener;
}
- protected final void incrementStats(SparseLongArray stats) {
- final int callingUid = Binder.getCallingUid();
- 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());
- }
- }
+ public ContactsTransaction getCurrentTransaction() {
+ return mTransactionHolder.get();
}
- protected final void incrementStats(SparseLongArray statsNonBatch,
- SparseLongArray statsInBatch) {
+ private boolean isInBatch() {
final ContactsTransaction t = mTransactionHolder.get();
- final boolean inBatch = t != null && t.isBatch();
- 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();
+ return t != null && t.isBatch();
}
@Override
public Uri insert(Uri uri, ContentValues values) {
- incrementStats(mInsertStats, mInsertInBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementInsertStats(callingUid, isInBatch());
try {
ContactsTransaction transaction = startTransaction(false);
try {
@@ -212,13 +168,14 @@ public abstract class AbstractContactsProvider extends ContentProvider
endTransaction(false);
}
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- incrementStats(mDeleteStats, mDeleteInBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementDeleteStats(callingUid, isInBatch());
try {
ContactsTransaction transaction = startTransaction(false);
try {
@@ -232,13 +189,14 @@ public abstract class AbstractContactsProvider extends ContentProvider
endTransaction(false);
}
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- incrementStats(mUpdateStats, mUpdateInBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementUpdateStats(callingUid, isInBatch());
try {
ContactsTransaction transaction = startTransaction(false);
try {
@@ -252,13 +210,14 @@ public abstract class AbstractContactsProvider extends ContentProvider
endTransaction(false);
}
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
- incrementStats(mBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementBatchStats(callingUid);
try {
ContactsTransaction transaction = startTransaction(true);
int numValues = values.length;
@@ -282,14 +241,15 @@ public abstract class AbstractContactsProvider extends ContentProvider
}
return numValues;
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
- incrementStats(mBatchStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementBatchStats(callingUid);
try {
if (VERBOSE_LOGGING) {
Log.v(TAG, "applyBatch: " + operations.size() + " ops");
@@ -331,7 +291,7 @@ public abstract class AbstractContactsProvider extends ContentProvider
endTransaction(true);
}
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
@@ -462,32 +422,7 @@ public abstract class AbstractContactsProvider extends ContentProvider
pw.print("Database: ");
pw.println(dbName);
- pw.print(" Uptime: ");
- pw.print((SystemClock.elapsedRealtime() - mStartTime) / (60 * 1000));
- pw.println(" minutes");
-
- synchronized (mStatsLock) {
- pw.println();
- pw.println(" Client activities:");
- pw.println(" UID Query Insert Update Delete Batch Insert Update Delete"
- + " Sec");
- for (int i = 0; i < mAllCallingUids.size(); i++) {
- final int uid = mAllCallingUids.keyAt(i);
- pw.println(String.format(
- " %-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)
- ));
- }
- }
+ mStats.dump(pw, " ");
if (mDbHelper == null) {
pw.println("mDbHelper is null");
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index 59e9b147..f71a7503 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -22,10 +22,13 @@ import static com.android.providers.contacts.util.DbQueryUtils.getInequalityClau
import android.app.AppOpsManager;
import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils;
@@ -45,11 +48,15 @@ import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ProviderAccessStats;
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.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -175,6 +182,10 @@ public class CallLogProvider extends ContentProvider {
private VoicemailPermissions mVoicemailPermissions;
private CallLogInsertionHelper mCallLogInsertionHelper;
+ private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<>();
+ private final ThreadLocal<Integer> mCallingUid = new ThreadLocal<>();
+ private final ProviderAccessStats mStats = new ProviderAccessStats();
+
protected boolean isShadow() {
return false;
}
@@ -228,9 +239,58 @@ public class CallLogProvider extends ContentProvider {
return CallLogDatabaseHelper.getInstance(context);
}
+ protected boolean applyingBatch() {
+ final Boolean applying = mApplyingBatch.get();
+ return applying != null && applying;
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ final int callingUid = Binder.getCallingUid();
+ mCallingUid.set(callingUid);
+
+ mStats.incrementBatchStats(callingUid);
+ mApplyingBatch.set(true);
+ try {
+ return super.applyBatch(operations);
+ } finally {
+ mApplyingBatch.set(false);
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ final int callingUid = Binder.getCallingUid();
+ mCallingUid.set(callingUid);
+
+ mStats.incrementBatchStats(callingUid);
+ mApplyingBatch.set(true);
+ try {
+ return super.bulkInsert(uri, values);
+ } finally {
+ mApplyingBatch.set(false);
+ mStats.finishOperation(callingUid);
+ }
+ }
+
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ // Note don't use mCallingUid here. That's only used by mutation functions.
+ final int callingUid = Binder.getCallingUid();
+
+ mStats.incrementQueryStats(callingUid);
+ try {
+ return queryInternal(uri, projection, selection, selectionArgs, sortOrder);
+ } finally {
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ private Cursor queryInternal(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) +
@@ -361,6 +421,44 @@ public class CallLogProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
+ final int callingUid =
+ applyingBatch() ? mCallingUid.get() : Binder.getCallingUid();
+
+ mStats.incrementInsertStats(callingUid, applyingBatch());
+ try {
+ return insertInternal(uri, values);
+ } finally {
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ final int callingUid =
+ applyingBatch() ? mCallingUid.get() : Binder.getCallingUid();
+
+ mStats.incrementInsertStats(callingUid, applyingBatch());
+ try {
+ return updateInternal(uri, values, selection, selectionArgs);
+ } finally {
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ final int callingUid =
+ applyingBatch() ? mCallingUid.get() : Binder.getCallingUid();
+
+ mStats.incrementInsertStats(callingUid, applyingBatch());
+ try {
+ return deleteInternal(uri, selection, selectionArgs);
+ } finally {
+ mStats.finishOperation(callingUid);
+ }
+ }
+
+ private Uri insertInternal(Uri uri, ContentValues values) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "insert: uri=" + uri + " values=[" + values + "]" +
" CPID=" + Binder.getCallingPid());
@@ -383,15 +481,15 @@ public class CallLogProvider extends ContentProvider {
// Add the computed fields to the copied values.
mCallLogInsertionHelper.addComputedValues(copiedValues);
- long rowId = getDatabaseModifier(mCallsInserter).insert(copiedValues);
+ long rowId = createDatabaseModifier(mCallsInserter).insert(copiedValues);
if (rowId > 0) {
return ContentUris.withAppendedId(uri, rowId);
}
return null;
}
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ private int updateInternal(Uri uri, ContentValues values,
+ String selection, String[] selectionArgs) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "update: uri=" + uri +
" selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
@@ -423,12 +521,11 @@ public class CallLogProvider extends ContentProvider {
throw new UnsupportedOperationException("Cannot update URL: " + uri);
}
- return getDatabaseModifier(db).update(uri, Tables.CALLS, values, selectionBuilder.build(),
+ return createDatabaseModifier(db).update(uri, Tables.CALLS, values, selectionBuilder.build(),
selectionArgs);
}
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
+ private int deleteInternal(Uri uri, String selection, String[] selectionArgs) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "delete: uri=" + uri +
" selection=[" + selection + "] args=" + Arrays.toString(selectionArgs) +
@@ -445,7 +542,7 @@ public class CallLogProvider extends ContentProvider {
case CALLS:
// TODO: Special case - We may want to forward the delete request on user 0 to the
// shadow provider too.
- return getDatabaseModifier(db).delete(Tables.CALLS,
+ return createDatabaseModifier(db).delete(Tables.CALLS,
selectionBuilder.build(), selectionArgs);
default:
throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
@@ -460,15 +557,15 @@ public class CallLogProvider extends ContentProvider {
* Returns a {@link DatabaseModifier} that takes care of sending necessary notifications
* after the operation is performed.
*/
- private DatabaseModifier getDatabaseModifier(SQLiteDatabase db) {
+ private DatabaseModifier createDatabaseModifier(SQLiteDatabase db) {
return new DbModifierWithNotification(Tables.CALLS, db, getContext());
}
/**
- * Same as {@link #getDatabaseModifier(SQLiteDatabase)} but used for insert helper operations
+ * Same as {@link #createDatabaseModifier(SQLiteDatabase)} but used for insert helper operations
* only.
*/
- private DatabaseModifier getDatabaseModifier(DatabaseUtils.InsertHelper insertHelper) {
+ private DatabaseModifier createDatabaseModifier(DatabaseUtils.InsertHelper insertHelper) {
return new DbModifierWithNotification(Tables.CALLS, insertHelper, getContext());
}
@@ -753,4 +850,9 @@ public class CallLogProvider extends ContentProvider {
public void shutdown() {
mTaskScheduler.shutdownForTest();
}
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mStats.dump(writer, " ");
+ }
}
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 76fe173d..9414ece8 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -136,9 +136,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 1000-1099 M
* 1100-1199 N
* 1200-1299 O
+ * 1300-1399 P
* </pre>
*/
- static final int DATABASE_VERSION = 1202;
+ static final int DATABASE_VERSION = 1300;
private static final int MINIMUM_SUPPORTED_VERSION = 700;
@VisibleForTesting
@@ -1440,7 +1441,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
Data.SYNC2 + " TEXT, " +
Data.SYNC3 + " TEXT, " +
Data.SYNC4 + " TEXT, " +
- Data.CARRIER_PRESENCE + " INTEGER NOT NULL DEFAULT 0 " +
+ Data.CARRIER_PRESENCE + " INTEGER NOT NULL DEFAULT 0, " +
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME + " TEXT, " +
+ Data.PREFERRED_PHONE_ACCOUNT_ID + " TEXT " +
");");
db.execSQL("CREATE INDEX data_raw_contact_id ON " + Tables.DATA + " (" +
@@ -1924,6 +1927,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ Data.DATA14 + ", "
+ Data.DATA15 + ", "
+ Data.CARRIER_PRESENCE + ", "
+ + Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME + ", "
+ + Data.PREFERRED_PHONE_ACCOUNT_ID + ", "
+ Data.SYNC1 + ", "
+ Data.SYNC2 + ", "
+ Data.SYNC3 + ", "
@@ -2666,6 +2671,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
oldVersion = 1202;
}
+ if (isUpgradeRequired(oldVersion,newVersion, 1300)) {
+ upgradeToVersion1300(db);
+ upgradeViewsAndTriggers = true;
+ oldVersion = 1300;
+ }
+
// We extracted "calls" and "voicemail_status" at this point, but we can't remove them here
// yet, until CallLogDatabaseHelper moves the data.
@@ -3438,6 +3449,15 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ "last_time_used = 0");
}
+ public void upgradeToVersion1300(SQLiteDatabase db) {
+ try {
+ db.execSQL("ALTER TABLE data ADD preferred_phone_account_component_name "
+ + "TEXT;");
+ db.execSQL("ALTER TABLE data ADD preferred_phone_account_id TEXT;");
+ } catch (SQLiteException ignore) {
+ }
+ }
+
/**
* 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
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index a5af51fe..54b4a27d 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -754,6 +754,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
.add(Data.DATA14)
.add(Data.DATA15)
.add(Data.CARRIER_PRESENCE)
+ .add(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME)
+ .add(Data.PREFERRED_PHONE_ACCOUNT_ID)
.add(Data.DATA_VERSION)
.add(Data.IS_PRIMARY)
.add(Data.IS_SUPER_PRIMARY)
@@ -4649,6 +4651,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
if (flagExists(values, RawContacts.STARRED)) {
if (!callerIsSyncAdapter) {
updateFavoritesMembership(rawContactId, flagIsSet(values, RawContacts.STARRED));
+ mTransactionContext.get().markRawContactDirtyAndChanged(
+ rawContactId, callerIsSyncAdapter);
+ mSyncToNetwork |= !callerIsSyncAdapter;
}
aggregator.updateStarred(rawContactId);
aggregator.updatePinned(rawContactId);
@@ -4662,6 +4667,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
SELECTION_STARRED_FROM_RAW_CONTACTS,
new String[] {Long.toString(rawContactId)});
updateFavoritesMembership(rawContactId, starred);
+ mTransactionContext.get().markRawContactDirtyAndChanged(
+ rawContactId, callerIsSyncAdapter);
+ mSyncToNetwork |= !callerIsSyncAdapter;
}
}
if (flagExists(values, RawContacts.SEND_TO_VOICEMAIL)) {
@@ -4832,6 +4840,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
if (hasStarredValue) {
updateFavoritesMembership(rawContactId,
flagIsSet(values, RawContacts.STARRED));
+ mSyncToNetwork |= !callerIsSyncAdapter;
}
if (hasStarredValue || hasPinnedValue || hasVoiceMailValue) {
@@ -5536,7 +5545,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
return mProfileProvider.query(uri, projection, selection, selectionArgs, sortOrder,
cancellationSignal);
}
- incrementStats(mQueryStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementQueryStats(callingUid);
try {
// Otherwise proceed with a normal query against the contacts DB.
switchToContactMode();
@@ -5544,7 +5554,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
return queryDirectoryIfNecessary(uri, projection, selection, selectionArgs, sortOrder,
cancellationSignal);
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
diff --git a/src/com/android/providers/contacts/DatabaseModifier.java b/src/com/android/providers/contacts/DatabaseModifier.java
index b11605b4..60f9c7f1 100644
--- a/src/com/android/providers/contacts/DatabaseModifier.java
+++ b/src/com/android/providers/contacts/DatabaseModifier.java
@@ -49,4 +49,10 @@ public interface DatabaseModifier {
* {@link SQLiteDatabase#delete(String, String, String[])} method.
*/
public abstract int delete(String table, String whereClause, String[] whereArgs);
+
+ void startBulkOperation();
+
+ void yieldBulkOperation();
+
+ void finishBulkOperation();
}
diff --git a/src/com/android/providers/contacts/DbModifierWithNotification.java b/src/com/android/providers/contacts/DbModifierWithNotification.java
index 7e7b3e17..852301d3 100644
--- a/src/com/android/providers/contacts/DbModifierWithNotification.java
+++ b/src/com/android/providers/contacts/DbModifierWithNotification.java
@@ -17,7 +17,6 @@
package com.android.providers.contacts;
-import static android.Manifest.permission.ADD_VOICEMAIL;
import static android.Manifest.permission.READ_VOICEMAIL;
import android.content.ComponentName;
@@ -25,8 +24,6 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.database.DatabaseUtils.InsertHelper;
import android.database.sqlite.SQLiteDatabase;
@@ -37,17 +34,15 @@ 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.common.io.MoreCloseables;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.providers.contacts.CallLogDatabaseHelper.Tables;
import com.android.providers.contacts.util.DbQueryUtils;
import com.google.android.collect.Lists;
import com.google.common.collect.Iterables;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
import java.util.Set;
/**
@@ -57,6 +52,7 @@ import java.util.Set;
* of then got affected by the change.
*/
public class DbModifierWithNotification implements DatabaseModifier {
+
private static final String TAG = "DbModifierWithNotify";
private static final String[] PROJECTION = new String[] {
@@ -73,8 +69,11 @@ public class DbModifierWithNotification implements DatabaseModifier {
private final Context mContext;
private final Uri mBaseUri;
private final boolean mIsCallsTable;
- private final VoicemailPermissions mVoicemailPermissions;
+ private final VoicemailNotifier mVoicemailNotifier;
+
+ private boolean mIsBulkOperation = false;
+ private static VoicemailNotifier sVoicemailNotifierForTest;
public DbModifierWithNotification(String tableName, SQLiteDatabase db, Context context) {
this(tableName, db, null, context);
@@ -94,7 +93,8 @@ public class DbModifierWithNotification implements DatabaseModifier {
mBaseUri = mTableName.equals(Tables.VOICEMAIL_STATUS) ?
Status.CONTENT_URI : Voicemails.CONTENT_URI;
mIsCallsTable = mTableName.equals(Tables.CALLS);
- mVoicemailPermissions = new VoicemailPermissions(mContext);
+ mVoicemailNotifier = sVoicemailNotifierForTest != null ? sVoicemailNotifierForTest
+ : new VoicemailNotifier(mContext, mBaseUri);
}
@Override
@@ -143,13 +143,21 @@ public class DbModifierWithNotification implements DatabaseModifier {
}
}
- private void notifyVoicemailChangeOnInsert(Uri notificationUri, Set<String> packagesModified) {
+ private void notifyVoicemailChangeOnInsert(
+ Uri notificationUri, Set<String> packagesModified) {
if (mIsCallsTable) {
- notifyVoicemailChange(notificationUri, packagesModified,
- VoicemailContract.ACTION_NEW_VOICEMAIL, Intent.ACTION_PROVIDER_CHANGED);
- } else {
- notifyVoicemailChange(notificationUri, packagesModified,
- Intent.ACTION_PROVIDER_CHANGED);
+ mVoicemailNotifier.addIntentActions(VoicemailContract.ACTION_NEW_VOICEMAIL);
+ }
+ notifyVoicemailChange(notificationUri, packagesModified);
+ }
+
+ private void notifyVoicemailChange(Uri notificationUri,
+ Set<String> modifiedPackages) {
+ mVoicemailNotifier.addUri(notificationUri);
+ mVoicemailNotifier.addModifiedPackages(modifiedPackages);
+ mVoicemailNotifier.addIntentActions(Intent.ACTION_PROVIDER_CHANGED);
+ if (!mIsBulkOperation) {
+ mVoicemailNotifier.sendNotification();
}
}
@@ -159,7 +167,8 @@ public class DbModifierWithNotification implements DatabaseModifier {
Set<String> packagesModified = getModifiedPackages(whereClause, whereArgs);
packagesModified.addAll(getModifiedPackages(values));
- boolean isVoicemail = packagesModified.size() != 0;
+ boolean isVoicemailContent =
+ packagesModified.size() != 0 && isUpdatingVoicemailColumns(values);
boolean hasMarkedRead = false;
if (mIsCallsTable) {
@@ -169,35 +178,27 @@ public class DbModifierWithNotification implements DatabaseModifier {
} else {
updateLastModified(table, whereClause, whereArgs);
}
- if (isVoicemail) {
- // If a calling package is modifying its own entries, it means that the change came
- // from the server and thus is synced or "clean". Otherwise, it means that a local
- // change is being made to the database, so the entries should be marked as "dirty"
- // so that the corresponding sync adapter knows they need to be synced.
- int isDirty;
- Integer callerSetDirty = values.getAsInteger(Voicemails.DIRTY);
- if (callerSetDirty != null) {
- // Respect the calling package if it sets the dirty flag
- isDirty = callerSetDirty == 0 ? 0 : 1;
- } else {
- isDirty = isSelfModifyingOrInternal(packagesModified) ? 0 : 1;
- }
- values.put(VoicemailContract.Voicemails.DIRTY, isDirty);
-
- if (isDirty == 0 && values.containsKey(Calls.IS_READ) && getAsBoolean(values,
- Calls.IS_READ)) {
- // If the server has set the IS_READ, it should also unset the new flag
- if (!values.containsKey(Calls.NEW)) {
- values.put(Calls.NEW, 0);
- hasMarkedRead = true;
+ if (isVoicemailContent) {
+ if (updateDirtyFlag(values, packagesModified)) {
+ if (values.containsKey(Calls.IS_READ)
+ && getAsBoolean(values,
+ Calls.IS_READ)) {
+ // If the server has set the IS_READ, it should also unset the new flag
+ if (!values.containsKey(Calls.NEW)) {
+ values.put(Calls.NEW, 0);
+ hasMarkedRead = true;
+ }
}
}
}
}
-
+ // updateDirtyFlag might remove the value and leave values empty.
+ if (values.isEmpty()) {
+ return 0;
+ }
int count = mDb.update(table, values, whereClause, whereArgs);
- if (count > 0 && isVoicemail) {
- notifyVoicemailChange(mBaseUri, packagesModified, Intent.ACTION_PROVIDER_CHANGED);
+ if (count > 0 && isVoicemailContent || Tables.VOICEMAIL_STATUS.equals(table)) {
+ notifyVoicemailChange(mBaseUri, packagesModified);
}
if (count > 0 && mIsCallsTable) {
notifyCallLogChange();
@@ -213,6 +214,38 @@ public class DbModifierWithNotification implements DatabaseModifier {
return count;
}
+ private boolean updateDirtyFlag(ContentValues values, Set<String> packagesModified) {
+ // If a calling package is modifying its own entries, it means that the change came
+ // from the server and thus is synced or "clean". Otherwise, it means that a local
+ // change is being made to the database, so the entries should be marked as "dirty"
+ // so that the corresponding sync adapter knows they need to be synced.
+ int isDirty;
+ Integer callerSetDirty = values.getAsInteger(Voicemails.DIRTY);
+ if (callerSetDirty != null) {
+ // Respect the calling package if it sets the dirty flag
+ if (callerSetDirty == Voicemails.DIRTY_RETAIN) {
+ values.remove(Voicemails.DIRTY);
+ return false;
+ } else {
+ isDirty = callerSetDirty == 0 ? 0 : 1;
+ }
+ } else {
+ isDirty = isSelfModifyingOrInternal(packagesModified) ? 0 : 1;
+ }
+
+ values.put(Voicemails.DIRTY, isDirty);
+ return isDirty == 0;
+ }
+
+ private boolean isUpdatingVoicemailColumns(ContentValues values) {
+ for (String key : values.keySet()) {
+ if (VoicemailContentTable.ALLOWED_COLUMNS.contains(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void updateLastModified(String table, String whereClause, String[] whereArgs) {
ContentValues values = new ContentValues();
values.put(Calls.LAST_MODIFIED, getTimeMillis());
@@ -247,7 +280,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
}
if (count > 0 && isVoicemail) {
- notifyVoicemailChange(mBaseUri, packagesModified, Intent.ACTION_PROVIDER_CHANGED);
+ notifyVoicemailChange(mBaseUri, packagesModified);
}
if (count > 0 && mIsCallsTable) {
notifyCallLogChange();
@@ -255,6 +288,25 @@ public class DbModifierWithNotification implements DatabaseModifier {
return count;
}
+ @Override
+ public void startBulkOperation() {
+ mIsBulkOperation = true;
+ mDb.beginTransaction();
+ }
+
+ @Override
+ public void yieldBulkOperation() {
+ mDb.yieldIfContendedSafely();
+ }
+
+ @Override
+ public void finishBulkOperation() {
+ mDb.setTransactionSuccessful();
+ mDb.endTransaction();
+ mIsBulkOperation = false;
+ mVoicemailNotifier.sendNotification();
+ }
+
/**
* Returns the set of packages affected when a modify operation is run for the specified
* where clause. When called from an insert operation an empty set returned by this method
@@ -266,7 +318,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
Cursor cursor = mDb.query(mTableName, PROJECTION,
DbQueryUtils.concatenateClauses(NON_NULL_SOURCE_PACKAGE_SELECTION, whereClause),
whereArgs, null, null, null);
- while(cursor.moveToNext()) {
+ while (cursor.moveToNext()) {
modifiedPackages.add(cursor.getString(SOURCE_PACKAGE_COLUMN_INDEX));
}
MoreCloseables.closeQuietly(cursor);
@@ -281,7 +333,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
*/
private Set<String> getModifiedPackages(ContentValues values) {
Set<String> impactedPackages = new ArraySet<>();
- if(values.containsKey(VoicemailContract.SOURCE_PACKAGE_FIELD)) {
+ if (values.containsKey(VoicemailContract.SOURCE_PACKAGE_FIELD)) {
impactedPackages.add(values.getAsString(VoicemailContract.SOURCE_PACKAGE_FIELD));
}
return impactedPackages;
@@ -290,7 +342,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
/**
* @param packagesModified source packages that inserted the voicemail that is being modified
* @return {@code true} if the caller is modifying its own voicemail, or this is an internal
- * transaction, {@code false} otherwise.
+ * transaction, {@code false} otherwise.
*/
private boolean isSelfModifyingOrInternal(Set<String> packagesModified) {
final Collection<String> callingPackages = getCallingPackages();
@@ -301,59 +353,7 @@ public class DbModifierWithNotification implements DatabaseModifier {
// but allows us to mock the results for testing.
return packagesModified.size() == 1 && (callingPackages.contains(
Iterables.getOnlyElement(packagesModified))
- || callingPackages.contains(mContext.getPackageName()));
- }
-
- private void notifyVoicemailChange(Uri notificationUri, Set<String> modifiedPackages,
- String... intentActions) {
- // Notify the observers.
- // Must be done only once, even if there are multiple broadcast intents.
- mContext.getContentResolver().notifyChange(notificationUri, null, true);
- Collection<String> callingPackages = getCallingPackages();
- // Now fire individual intents.
- for (String intentAction : intentActions) {
- // self_change extra should be included only for provider_changed events.
- boolean includeSelfChangeExtra = intentAction.equals(Intent.ACTION_PROVIDER_CHANGED);
- for (ComponentName component :
- getBroadcastReceiverComponents(intentAction, notificationUri)) {
- // Ignore any package that is not affected by the change and don't have full access
- // either.
- if (!modifiedPackages.contains(component.getPackageName()) &&
- !mVoicemailPermissions.packageHasReadAccess(
- component.getPackageName())) {
- continue;
- }
-
- Intent intent = new Intent(intentAction, notificationUri);
- intent.setComponent(component);
- if (includeSelfChangeExtra && callingPackages != null) {
- intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
- callingPackages.contains(component.getPackageName()));
- }
- String permissionNeeded = modifiedPackages.contains(component.getPackageName()) ?
- ADD_VOICEMAIL : READ_VOICEMAIL;
- mContext.sendBroadcast(intent, permissionNeeded);
- Log.v(TAG, String.format("Sent intent. act:%s, url:%s, comp:%s, perm:%s," +
- " self_change:%s", intent.getAction(), intent.getData(),
- component.getClassName(), permissionNeeded,
- intent.hasExtra(VoicemailContract.EXTRA_SELF_CHANGE) ?
- intent.getBooleanExtra(VoicemailContract.EXTRA_SELF_CHANGE, false) :
- null));
- }
- }
- }
-
- /** Determines the components that can possibly receive the specified intent. */
- private List<ComponentName> getBroadcastReceiverComponents(String intentAction, Uri uri) {
- Intent intent = new Intent(intentAction, uri);
- List<ComponentName> receiverComponents = new ArrayList<ComponentName>();
- // For broadcast receivers ResolveInfo.activityInfo is the one that is populated.
- for (ResolveInfo resolveInfo :
- mContext.getPackageManager().queryBroadcastReceivers(intent, 0)) {
- ActivityInfo activityInfo = resolveInfo.activityInfo;
- receiverComponents.add(new ComponentName(activityInfo.packageName, activityInfo.name));
- }
- return receiverComponents;
+ || callingPackages.contains(mContext.getPackageName()));
}
/**
@@ -393,4 +393,9 @@ public class DbModifierWithNotification implements DatabaseModifier {
}
return CallLogProvider.getTimeForTestMillis();
}
+
+ @VisibleForTesting
+ static void setVoicemailNotifierForTest(VoicemailNotifier notifier) {
+ sVoicemailNotifierForTest = notifier;
+ }
}
diff --git a/src/com/android/providers/contacts/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java
index 6c84e4b0..f3b6daf6 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -24,6 +24,7 @@ import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
+import android.os.Binder;
import android.os.CancellationSignal;
import android.provider.ContactsContract.Intents;
@@ -70,12 +71,13 @@ public class ProfileProvider extends AbstractContactsProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder, CancellationSignal cancellationSignal) {
- incrementStats(mQueryStats);
+ final int callingUid = Binder.getCallingUid();
+ mStats.incrementQueryStats(callingUid);
try {
return mDelegate.queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1,
cancellationSignal);
} finally {
- finishOperation();
+ mStats.finishOperation(callingUid);
}
}
diff --git a/src/com/android/providers/contacts/SearchIndexManager.java b/src/com/android/providers/contacts/SearchIndexManager.java
index 14c78a77..e421654c 100644
--- a/src/com/android/providers/contacts/SearchIndexManager.java
+++ b/src/com/android/providers/contacts/SearchIndexManager.java
@@ -24,7 +24,6 @@ import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.ProviderStatus;
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -35,6 +34,8 @@ import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.util.CappedStringBuilder;
+
import com.google.android.collect.Lists;
import com.google.common.annotations.VisibleForTesting;
@@ -51,6 +52,8 @@ public class SearchIndexManager {
private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final int MAX_STRING_BUILDER_SIZE = 1024 * 10;
+
public static final String PROPERTY_SEARCH_INDEX_VERSION = "search_index";
private static final int SEARCH_INDEX_VERSION = 1;
@@ -72,10 +75,11 @@ public class SearchIndexManager {
public static final int SEPARATOR_SLASH = 2;
public static final int SEPARATOR_COMMA = 3;
- private StringBuilder mSbContent = new StringBuilder();
- private StringBuilder mSbName = new StringBuilder();
- private StringBuilder mSbTokens = new StringBuilder();
- private StringBuilder mSbElementContent = new StringBuilder();
+ private CappedStringBuilder mSbContent = new CappedStringBuilder(MAX_STRING_BUILDER_SIZE);
+ private CappedStringBuilder mSbName = new CappedStringBuilder(MAX_STRING_BUILDER_SIZE);
+ private CappedStringBuilder mSbTokens = new CappedStringBuilder(MAX_STRING_BUILDER_SIZE);
+ private CappedStringBuilder mSbElementContent = new CappedStringBuilder(
+ MAX_STRING_BUILDER_SIZE);
private ArraySet<String> mUniqueElements = new ArraySet<>();
private Cursor mCursor;
@@ -84,10 +88,10 @@ public class SearchIndexManager {
}
void reset() {
- mSbContent.setLength(0);
- mSbTokens.setLength(0);
- mSbName.setLength(0);
- mSbElementContent.setLength(0);
+ mSbContent.clear();
+ mSbTokens.clear();
+ mSbName.clear();
+ mSbElementContent.clear();
mUniqueElements.clear();
}
@@ -126,7 +130,7 @@ public class SearchIndexManager {
mSbContent.append(content);
mUniqueElements.add(content);
}
- mSbElementContent.setLength(0);
+ mSbElementContent.clear();
}
}
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index 160a1a99..1ced1be6 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -15,20 +15,22 @@
*/
package com.android.providers.contacts;
+import static android.app.AppOpsManager.MODE_ALLOWED;
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.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.ContentProvider;
-import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.provider.VoicemailContract;
@@ -49,7 +51,6 @@ 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
@@ -82,8 +83,15 @@ public class VoicemailContentProvider extends ContentProvider
}
Context context = context();
- // ADD_VOICEMAIL permission guards read and write. We do the same with app ops.
- // The permission name doesn't reflect its function but we cannot rename it.
+ // Read and write permission requires ADD_VOICEMAIL or carrier privileges. We can't declare
+ // any permission entries in the manifest because carrier-privileged apps without
+ // ADD_VOICEMAIL would be blocked by the platform without even reaching our custom
+ // enforce{Read,Write}PermissionInner functions. These overrides are what allow carrier-
+ // privileged apps to bypass these runtime-configured permissions.
+ // TODO(b/74245334): See if these can be removed since individual operations perform their
+ // own checks.
+ setReadPermission(android.Manifest.permission.ADD_VOICEMAIL);
+ setWritePermission(android.Manifest.permission.ADD_VOICEMAIL);
setAppOps(AppOpsManager.OP_ADD_VOICEMAIL, AppOpsManager.OP_ADD_VOICEMAIL);
mVoicemailPermissions = new VoicemailPermissions(context);
@@ -109,6 +117,27 @@ public class VoicemailContentProvider extends ContentProvider
return true;
}
+ @Override
+ protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken)
+ throws SecurityException {
+ // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
+ if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
+ return MODE_ALLOWED;
+ }
+ return super.enforceReadPermissionInner(uri, callingPkg, callerToken);
+ }
+
+
+ @Override
+ protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken)
+ throws SecurityException {
+ // Permit carrier-privileged apps regardless of ADD_VOICEMAIL permission state.
+ if (mVoicemailPermissions.callerHasCarrierPrivileges()) {
+ return MODE_ALLOWED;
+ }
+ return super.enforceWritePermissionInner(uri, callingPkg, callerToken);
+ }
+
@VisibleForTesting
void scheduleScanStalePackages() {
scheduleTask(BACKGROUND_TASK_SCAN_STALE_PACKAGES, null);
@@ -157,6 +186,12 @@ public class VoicemailContentProvider extends ContentProvider
}
@Override
+ public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
+ UriData uriData = checkPermissionsAndCreateUriDataForWrite(uri, values);
+ return getTableDelegate(uriData).bulkInsert(uriData, values);
+ }
+
+ @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
if (VERBOSE_LOGGING) {
diff --git a/src/com/android/providers/contacts/VoicemailContentTable.java b/src/com/android/providers/contacts/VoicemailContentTable.java
index 09a8c1f0..67dda926 100644
--- a/src/com/android/providers/contacts/VoicemailContentTable.java
+++ b/src/com/android/providers/contacts/VoicemailContentTable.java
@@ -39,7 +39,6 @@ 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;
@@ -52,16 +51,19 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
private static final String TAG = "VmContentProvider";
private final ProjectionMap mVoicemailProjectionMap;
- /** The private directory in which to store the data associated with the voicemail. */
+ /**
+ * The private directory in which to store the data associated with the voicemail.
+ */
private static final String DATA_DIRECTORY = "voicemail-data";
- private static final String[] FILENAME_ONLY_PROJECTION = new String[] { Voicemails._DATA };
+ private static final String[] FILENAME_ONLY_PROJECTION = new String[] {Voicemails._DATA};
- private static final ImmutableSet<String> ALLOWED_COLUMNS = new ImmutableSet.Builder<String>()
+ public static final ImmutableSet<String> ALLOWED_COLUMNS = new ImmutableSet.Builder<String>()
.add(Voicemails._ID)
.add(Voicemails.NUMBER)
.add(Voicemails.DATE)
.add(Voicemails.DURATION)
+ .add(Voicemails.NEW)
.add(Voicemails.IS_READ)
.add(Voicemails.TRANSCRIPTION)
.add(Voicemails.TRANSCRIPTION_STATE)
@@ -83,6 +85,8 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
.add(OpenableColumns.SIZE)
.build();
+ private static final int BULK_INSERTS_PER_YIELD_POINT = 50;
+
private final String mTableName;
private final CallLogDatabaseHelper mDbHelper;
private final Context mContext;
@@ -101,6 +105,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
.add(Voicemails.NUMBER)
.add(Voicemails.DATE)
.add(Voicemails.DURATION)
+ .add(Voicemails.NEW)
.add(Voicemails.IS_READ)
.add(Voicemails.TRANSCRIPTION)
.add(Voicemails.TRANSCRIPTION_STATE)
@@ -138,6 +143,30 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
@Override
public Uri insert(UriData uriData, ContentValues values) {
+ DatabaseModifier modifier = createDatabaseModifier(mDbHelper.getWritableDatabase());
+ Uri uri = insertRow(modifier, uriData, values);
+ return uri;
+ }
+
+ @Override
+ public int bulkInsert(UriData uriData, ContentValues[] values) {
+ DatabaseModifier modifier = createDatabaseModifier(mDbHelper.getWritableDatabase());
+ modifier.startBulkOperation();
+ int count = 0;
+ for (ContentValues value : values) {
+ Uri uri = insertRow(modifier, uriData, value);
+ if (uri != null) {
+ count++;
+ }
+ if((count % BULK_INSERTS_PER_YIELD_POINT) == 0){
+ modifier.yieldBulkOperation();
+ }
+ }
+ modifier.finishBulkOperation();
+ return count;
+ }
+
+ private Uri insertRow(DatabaseModifier modifier, UriData uriData, ContentValues values) {
checkForSupportedColumns(mVoicemailProjectionMap, values);
ContentValues copiedValues = new ContentValues(values);
checkInsertSupported(uriData);
@@ -160,7 +189,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
}
SQLiteDatabase db = mDbHelper.getWritableDatabase();
- long rowId = getDatabaseModifier(db).insert(mTableName, null, copiedValues);
+ long rowId = modifier.insert(mTableName, null, copiedValues);
if (rowId > 0) {
Uri newUri = ContentUris.withAppendedId(uriData.getUri(), rowId);
// Populate the 'voicemail_uri' field to be used by the call_log provider.
@@ -228,7 +257,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
}
// Now delete the rows themselves.
- return getDatabaseModifier(db).delete(mTableName, combinedClause,
+ return createDatabaseModifier(db).delete(mTableName, combinedClause,
selectionArgs);
}
@@ -262,7 +291,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
// URI that include message Id. I think we do want to support bulk update.
String combinedClause = concatenateClauses(selection, uriData.getWhereClause(),
getCallTypeClause());
- return getDatabaseModifier(db).update(uriData.getUri(), mTableName, values, combinedClause,
+ return createDatabaseModifier(db).update(uriData.getUri(), mTableName, values, combinedClause,
selectionArgs);
}
@@ -298,7 +327,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate {
return getEqualityClause(Calls.TYPE, Calls.VOICEMAIL_TYPE);
}
- private DatabaseModifier getDatabaseModifier(SQLiteDatabase db) {
+ private DatabaseModifier createDatabaseModifier(SQLiteDatabase db) {
return new DbModifierWithNotification(mTableName, db, mContext);
}
diff --git a/src/com/android/providers/contacts/VoicemailNotifier.java b/src/com/android/providers/contacts/VoicemailNotifier.java
new file mode 100644
index 00000000..159cec73
--- /dev/null
+++ b/src/com/android/providers/contacts/VoicemailNotifier.java
@@ -0,0 +1,128 @@
+package com.android.providers.contacts;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.provider.VoicemailContract;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Aggregates voicemail broadcasts from multiple operations in to a single one. The URIs will be
+ * {@link VoicemailContract.Voicemails#DIR_TYPE} instead of {@link
+ * VoicemailContract.Voicemails#ITEM_TYPE} if multiple URIs is notified.
+ */
+public class VoicemailNotifier {
+
+ private final String TAG = "VoicemailNotifier";
+
+ private final Context mContext;
+ private final Uri mBaseUri;
+
+ private final VoicemailPermissions mVoicemailPermissions;
+
+ private final Set<String> mIntentActions = new ArraySet<>();
+ private final Set<String> mModifiedPackages = new ArraySet<>();
+ private final Set<Uri> mUris = new ArraySet<>();
+
+ public VoicemailNotifier(Context context, Uri baseUri) {
+ mContext = context;
+ mBaseUri = baseUri;
+ mVoicemailPermissions = new VoicemailPermissions(mContext);
+ }
+
+ public void addIntentActions(String action) {
+ mIntentActions.add(action);
+ }
+
+ public void addModifiedPackages(Collection<String> packages) {
+ mModifiedPackages.addAll(packages);
+ }
+
+ public void addUri(Uri uri) {
+ mUris.add(uri);
+ }
+
+ public void sendNotification() {
+ Uri uri = mUris.size() == 1 ? mUris.iterator().next() : mBaseUri;
+ mContext.getContentResolver().notifyChange(uri, null, true);
+ Collection<String> callingPackages = getCallingPackages();
+ // Now fire individual intents.
+ for (String intentAction : mIntentActions) {
+ // self_change extra should be included only for provider_changed events.
+ boolean includeSelfChangeExtra = intentAction.equals(Intent.ACTION_PROVIDER_CHANGED);
+ Log.i(TAG, "receivers for " + intentAction + " :" + getBroadcastReceiverComponents(
+ intentAction, uri));
+ for (ComponentName component :
+ getBroadcastReceiverComponents(intentAction, uri)) {
+ boolean hasFullReadAccess =
+ mVoicemailPermissions.packageHasReadAccess(component.getPackageName());
+ boolean hasOwnAccess =
+ mVoicemailPermissions.packageHasOwnVoicemailAccess(
+ component.getPackageName());
+ // If we don't have full access, ignore the broadcast if the package isn't affected
+ // by the change or doesn't have access to its own messages.
+ if (!hasFullReadAccess
+ && (!mModifiedPackages.contains(component.getPackageName())
+ || !hasOwnAccess)) {
+ continue;
+ }
+
+ Intent intent = new Intent(intentAction, uri);
+ intent.setComponent(component);
+ if (includeSelfChangeExtra && callingPackages != null) {
+ intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
+ callingPackages.contains(component.getPackageName()));
+ }
+ mContext.sendBroadcast(intent);
+ Log.v(TAG, String.format("Sent intent. act:%s, url:%s, comp:%s," +
+ " self_change:%s", intent.getAction(), intent.getData(),
+ component.getClassName(),
+ intent.hasExtra(VoicemailContract.EXTRA_SELF_CHANGE) ?
+ intent.getBooleanExtra(VoicemailContract.EXTRA_SELF_CHANGE, false) :
+ null));
+ }
+ }
+ mIntentActions.clear();
+ mModifiedPackages.clear();
+ mUris.clear();
+ }
+
+ /**
+ * Returns the package names of the calling process. If the calling process has more than
+ * one packages, this returns them all
+ */
+ private Collection<String> getCallingPackages() {
+ int caller = Binder.getCallingUid();
+ if (caller == 0) {
+ return null;
+ }
+ return Lists.newArrayList(mContext.getPackageManager().getPackagesForUid(caller));
+ }
+
+ /**
+ * Determines the components that can possibly receive the specified intent.
+ */
+ private List<ComponentName> getBroadcastReceiverComponents(String intentAction, Uri uri) {
+ Intent intent = new Intent(intentAction, uri);
+ List<ComponentName> receiverComponents = new ArrayList<ComponentName>();
+ // For broadcast receivers ResolveInfo.activityInfo is the one that is populated.
+ for (ResolveInfo resolveInfo :
+ mContext.getPackageManager().queryBroadcastReceivers(intent, 0)) {
+ ActivityInfo activityInfo = resolveInfo.activityInfo;
+ receiverComponents.add(new ComponentName(activityInfo.packageName, activityInfo.name));
+ }
+ return receiverComponents;
+ }
+}
diff --git a/src/com/android/providers/contacts/VoicemailPermissions.java b/src/com/android/providers/contacts/VoicemailPermissions.java
index 50f2447a..ed3815dc 100644
--- a/src/com/android/providers/contacts/VoicemailPermissions.java
+++ b/src/com/android/providers/contacts/VoicemailPermissions.java
@@ -16,10 +16,12 @@
package com.android.providers.contacts;
-import com.android.providers.contacts.util.ContactsPermissions;
-
import android.content.Context;
+import android.os.Binder;
import android.telecom.DefaultDialerManager;
+import android.telephony.TelephonyManager;
+
+import com.android.providers.contacts.util.ContactsPermissions;
/**
* Provides method related to check various voicemail permissions under the
@@ -35,7 +37,8 @@ public class VoicemailPermissions {
/** Determines if the calling process has access to its own voicemails. */
public boolean callerHasOwnVoicemailAccess() {
- return callerHasPermission(android.Manifest.permission.ADD_VOICEMAIL);
+ return callerHasPermission(android.Manifest.permission.ADD_VOICEMAIL)
+ || callerHasCarrierPrivileges();
}
/** Determine if the calling process has full read access to all voicemails. */
@@ -63,7 +66,7 @@ public class VoicemailPermissions {
public void checkCallerHasOwnVoicemailAccess() {
if (!callerHasOwnVoicemailAccess()) {
throw new SecurityException("The caller must have permission: " +
- android.Manifest.permission.ADD_VOICEMAIL);
+ android.Manifest.permission.ADD_VOICEMAIL + " or carrier privileges");
}
}
@@ -91,7 +94,8 @@ public class VoicemailPermissions {
/** Determines if the given package has access to its own voicemails. */
public boolean packageHasOwnVoicemailAccess(String packageName) {
return packageHasPermission(packageName,
- android.Manifest.permission.ADD_VOICEMAIL);
+ android.Manifest.permission.ADD_VOICEMAIL)
+ || packageHasCarrierPrivileges(packageName);
}
/** Determines if the given package has read access. */
@@ -113,4 +117,25 @@ public class VoicemailPermissions {
private boolean callerHasPermission(String permission) {
return ContactsPermissions.hasCallerOrSelfPermission(mContext, permission);
}
+
+ /** Determines if the calling process has carrier privileges. */
+ public boolean callerHasCarrierPrivileges() {
+ TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ String[] packages = mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
+ for (String packageName : packages) {
+ if (tm.checkCarrierPrivilegesForPackageAnyPhone(packageName)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Determines if the given package has carrier privileges. */
+ private boolean packageHasCarrierPrivileges(String packageName) {
+ TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ return tm.getPackagesWithCarrierPrivileges().contains(packageName);
+ }
}
diff --git a/src/com/android/providers/contacts/VoicemailStatusTable.java b/src/com/android/providers/contacts/VoicemailStatusTable.java
index f3008c0e..5b03c8a6 100644
--- a/src/com/android/providers/contacts/VoicemailStatusTable.java
+++ b/src/com/android/providers/contacts/VoicemailStatusTable.java
@@ -32,8 +32,6 @@ 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.
*
@@ -78,7 +76,7 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
// Try to update before insert.
String combinedClause = uriData.getWhereClause();
- int rowsChanged = getDatabaseModifier(db)
+ int rowsChanged = createDatabaseModifier(db)
.update(uriData.getUri(), mTableName, values, combinedClause, null);
if (rowsChanged != 0) {
final String[] selection = new String[] {Status._ID};
@@ -90,7 +88,7 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
}
ContentValues copiedValues = new ContentValues(values);
mDelegateHelper.checkAndAddSourcePackageIntoValues(uriData, copiedValues);
- long rowId = getDatabaseModifier(db).insert(mTableName, null, copiedValues);
+ long rowId = createDatabaseModifier(db).insert(mTableName, null, copiedValues);
if (rowId > 0) {
return ContentUris.withAppendedId(uriData.getUri(), rowId);
} else {
@@ -100,11 +98,23 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
}
@Override
+ public int bulkInsert(UriData uriData, ContentValues[] values) {
+ int count = 0;
+ for (ContentValues value : values) {
+ Uri uri = insert(uriData, value);
+ if (uri != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ @Override
public int delete(UriData uriData, String selection, String[] selectionArgs) {
synchronized (DATABASE_LOCK) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
String combinedClause = concatenateClauses(selection, uriData.getWhereClause());
- return getDatabaseModifier(db).delete(mTableName, combinedClause,
+ return createDatabaseModifier(db).delete(mTableName, combinedClause,
selectionArgs);
}
}
@@ -135,7 +145,7 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
synchronized (DATABASE_LOCK) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
String combinedClause = concatenateClauses(selection, uriData.getWhereClause());
- return getDatabaseModifier(db)
+ return createDatabaseModifier(db)
.update(uriData.getUri(), mTableName, values, combinedClause, selectionArgs);
}
}
@@ -154,7 +164,7 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate {
throw new UnsupportedOperationException("File operation is not supported for status table");
}
- private DatabaseModifier getDatabaseModifier(SQLiteDatabase db) {
+ private DatabaseModifier createDatabaseModifier(SQLiteDatabase db) {
return new DbModifierWithNotification(mTableName, db, mContext);
}
diff --git a/src/com/android/providers/contacts/VoicemailTable.java b/src/com/android/providers/contacts/VoicemailTable.java
index fcb653ce..f71b50de 100644
--- a/src/com/android/providers/contacts/VoicemailTable.java
+++ b/src/com/android/providers/contacts/VoicemailTable.java
@@ -46,6 +46,8 @@ public interface VoicemailTable {
public ParcelFileDescriptor openFile(UriData uriData, String mode)
throws FileNotFoundException;
public ArraySet<String> getSourcePackages();
+
+ int bulkInsert(UriData uriData, ContentValues[] values);
}
/**
diff --git a/src/com/android/providers/contacts/util/CappedStringBuilder.java b/src/com/android/providers/contacts/util/CappedStringBuilder.java
new file mode 100644
index 00000000..74b70cf5
--- /dev/null
+++ b/src/com/android/providers/contacts/util/CappedStringBuilder.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 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.util.Log;
+
+import com.android.providers.contacts.AbstractContactsProvider;
+
+public class CappedStringBuilder {
+ private final int mCapSize;
+ private boolean mOver;
+ private final StringBuilder mStringBuilder = new StringBuilder();
+
+ public CappedStringBuilder(int capSize) {
+ mCapSize = capSize;
+ }
+
+ public void clear() {
+ mOver = false;
+ mStringBuilder.setLength(0);
+ }
+
+ public int length() {
+ return mStringBuilder.length();
+ }
+
+ @Override
+ public String toString() {
+ return mStringBuilder.toString();
+ }
+
+ public CappedStringBuilder append(char ch) {
+ if (canAppend(mStringBuilder.length() + 1)) {
+ mStringBuilder.append(ch);
+ }
+ return this;
+ }
+
+ public CappedStringBuilder append(String s) {
+ if (canAppend(mStringBuilder.length() + s.length())) {
+ mStringBuilder.append(s);
+ }
+ return this;
+ }
+
+ private boolean canAppend(int length) {
+ if (mOver || length > mCapSize) {
+ if (!mOver && AbstractContactsProvider.VERBOSE_LOGGING) {
+ Log.w(AbstractContactsProvider.TAG, "String too long! new length=" + length);
+ }
+ mOver = true;
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index 539b4368..02ea81e9 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -7,10 +7,9 @@ LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
ContactsProviderTestUtils \
android-support-test \
- mockito-target-minus-junit4 \
- legacy-android-test
+ mockito-target-minus-junit4
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index 46e97bc4..3c4a3abc 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -23,5 +23,6 @@
<test class="com.android.tradefed.testtype.InstrumentationTest" >
<option name="package" value="com.android.providers.contacts.tests" />
<option name="runner" value="android.test.InstrumentationTestRunner" />
+ <option name="hidden-api-checks" value="false"/>
</test>
</configuration>
diff --git a/tests/src/com/android/providers/contacts/BaseVoicemailProviderTest.java b/tests/src/com/android/providers/contacts/BaseVoicemailProviderTest.java
index 1020da89..665f68ba 100644
--- a/tests/src/com/android/providers/contacts/BaseVoicemailProviderTest.java
+++ b/tests/src/com/android/providers/contacts/BaseVoicemailProviderTest.java
@@ -70,6 +70,15 @@ public abstract class BaseVoicemailProviderTest extends BaseContactsProvider2Tes
mActor.removePermissions(READ_VOICEMAIL_PERMISSION);
mActor.removePermissions(WRITE_VOICEMAIL_PERMISSION);
mActor.addPermissions(ADD_VOICEMAIL_PERMISSION);
+ mActor.revokeCarrierPrivileges();
+ mUseSourceUri = true;
+ }
+
+ protected void setUpForOwnPermissionViaCarrierPrivileges() {
+ mActor.removePermissions(READ_VOICEMAIL_PERMISSION);
+ mActor.removePermissions(WRITE_VOICEMAIL_PERMISSION);
+ mActor.removePermissions(ADD_VOICEMAIL_PERMISSION);
+ mActor.grantCarrierPrivileges();
mUseSourceUri = true;
}
@@ -77,6 +86,7 @@ public abstract class BaseVoicemailProviderTest extends BaseContactsProvider2Tes
mActor.addPermissions(ADD_VOICEMAIL_PERMISSION);
mActor.addPermissions(READ_VOICEMAIL_PERMISSION);
mActor.addPermissions(WRITE_VOICEMAIL_PERMISSION);
+ mActor.revokeCarrierPrivileges();
mUseSourceUri = false;
}
@@ -84,6 +94,7 @@ public abstract class BaseVoicemailProviderTest extends BaseContactsProvider2Tes
mActor.removePermissions(ADD_VOICEMAIL_PERMISSION);
mActor.removePermissions(READ_VOICEMAIL_PERMISSION);
mActor.removePermissions(WRITE_VOICEMAIL_PERMISSION);
+ mActor.revokeCarrierPrivileges();
mUseSourceUri = true;
}
diff --git a/tests/src/com/android/providers/contacts/ContactsActor.java b/tests/src/com/android/providers/contacts/ContactsActor.java
index 11a13c27..470f64b7 100644
--- a/tests/src/com/android/providers/contacts/ContactsActor.java
+++ b/tests/src/com/android/providers/contacts/ContactsActor.java
@@ -57,9 +57,11 @@ import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.StatusUpdates;
+import android.telephony.TelephonyManager;
import android.test.IsolatedContext;
import android.test.mock.MockContentResolver;
import android.test.mock.MockContext;
+import android.text.TextUtils;
import com.android.providers.contacts.util.ContactsPermissions;
import com.android.providers.contacts.util.MockSharedPreferences;
@@ -70,6 +72,7 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -98,6 +101,7 @@ public class ContactsActor {
private Set<String> mGrantedPermissions = Sets.newHashSet();
private final Set<Uri> mGrantedUriPermissions = Sets.newHashSet();
+ private boolean mHasCarrierPrivileges;
private List<ContentProvider> mAllProviders = new ArrayList<>();
@@ -242,6 +246,31 @@ public class ContactsActor {
}
}
+ private MockTelephonyManager mMockTelephonyManager;
+
+ private class MockTelephonyManager extends TelephonyManager {
+ public MockTelephonyManager(Context context) {
+ super(context);
+ }
+
+ @Override
+ public int checkCarrierPrivilegesForPackageAnyPhone(String packageName) {
+ if (TextUtils.equals(packageName, ContactsActor.this.packageName)
+ && mHasCarrierPrivileges) {
+ return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ }
+ return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+ }
+
+ @Override
+ public List<String> getPackagesWithCarrierPrivileges() {
+ if (!mHasCarrierPrivileges) {
+ return Collections.emptyList();
+ }
+ return Collections.singletonList(packageName);
+ }
+ }
+
/**
* A context wrapper that reports a different user id.
*
@@ -290,6 +319,9 @@ public class ContactsActor {
if (Context.USER_SERVICE.equals(name)) {
return mockUserManager;
}
+ if (Context.TELEPHONY_SERVICE.equals(name)) {
+ return mMockTelephonyManager;
+ }
// Use overallContext here; super.getSystemService() somehow won't return
// DevicePolicyManager.
return overallContext.getSystemService(name);
@@ -334,6 +366,9 @@ public class ContactsActor {
if (Context.USER_SERVICE.equals(name)) {
return mockUserManager;
}
+ if (Context.TELEPHONY_SERVICE.equals(name)) {
+ return mMockTelephonyManager;
+ }
// Use overallContext here; super.getSystemService() somehow won't return
// DevicePolicyManager.
return overallContext.getSystemService(name);
@@ -367,6 +402,7 @@ public class ContactsActor {
mMockAccountManager = new MockAccountManager(mProviderContext);
mockUserManager = new MockUserManager(mProviderContext);
+ mMockTelephonyManager = new MockTelephonyManager(mProviderContext);
provider = addProvider(providerClass, authority);
}
@@ -427,6 +463,14 @@ public class ContactsActor {
mGrantedUriPermissions.removeAll(Arrays.asList(uris));
}
+ public void grantCarrierPrivileges() {
+ mHasCarrierPrivileges = true;
+ }
+
+ public void revokeCarrierPrivileges() {
+ mHasCarrierPrivileges = false;
+ }
+
/**
* Mock {@link Context} that reports specific well-known values for testing
* data protection. The creator can override the owner package name, and
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
index 185fa031..1832b4e4 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
@@ -16,7 +16,6 @@
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;
@@ -67,14 +66,6 @@ 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;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.HashMap;
-
/**
* Unit tests for database create/upgrade operations in {@link ContactsDatabaseHelper}.
*
@@ -376,6 +367,8 @@ public class ContactsDatabaseHelperUpgradeTest extends BaseDatabaseHelperUpgrade
new TableColumn(Data.SYNC3, TEXT, false, null),
new TableColumn(Data.SYNC4, TEXT, false, null),
new TableColumn(Data.CARRIER_PRESENCE, INTEGER, true, "0"),
+ new TableColumn(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, TEXT, false, null),
+ new TableColumn(Data.PREFERRED_PHONE_ACCOUNT_ID, TEXT, false, null),
};
private static final TableColumn[] PHONE_LOOKUP_COLUMNS = new TableColumn[] {
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 8930338e..6c76709e 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -16,7 +16,6 @@
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;
@@ -420,6 +419,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
Data.DATA14,
Data.DATA15,
Data.CARRIER_PRESENCE,
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ Data.PREFERRED_PHONE_ACCOUNT_ID,
Data.SYNC1,
Data.SYNC2,
Data.SYNC3,
@@ -508,6 +509,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
Data.DATA14,
Data.DATA15,
Data.CARRIER_PRESENCE,
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ Data.PREFERRED_PHONE_ACCOUNT_ID,
Data.SYNC1,
Data.SYNC2,
Data.SYNC3,
@@ -590,6 +593,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
Data.DATA14,
Data.DATA15,
Data.CARRIER_PRESENCE,
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ Data.PREFERRED_PHONE_ACCOUNT_ID,
Data.SYNC1,
Data.SYNC2,
Data.SYNC3,
@@ -698,6 +703,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
Data.DATA14,
Data.DATA15,
Data.CARRIER_PRESENCE,
+ Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME,
+ Data.PREFERRED_PHONE_ACCOUNT_ID,
Data.SYNC1,
Data.SYNC2,
Data.SYNC3,
@@ -6485,6 +6492,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
values.put(Data.DATA14, "old14");
values.put(Data.DATA15, "old15");
values.put(Data.CARRIER_PRESENCE, 0);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, "oldcomponentname");
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_ID, "oldid");
Uri uri = mResolver.insert(Data.CONTENT_URI, values);
assertStoredValues(uri, values);
assertNetworkNotified(true);
@@ -6509,6 +6518,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
values.put(Data.DATA14, "new14");
values.put(Data.DATA15, "new15");
values.put(Data.CARRIER_PRESENCE, Data.CARRIER_PRESENCE_VT_CAPABLE);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, "newcomponentname");
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_ID, "newid");
mResolver.update(Data.CONTENT_URI, values, Data.RAW_CONTACT_ID + "=" + rawContactId +
" AND " + Data.MIMETYPE + "='testmimetype'", null);
assertNetworkNotified(true);
@@ -7021,7 +7032,6 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
values.put(ContactsContract.RawContacts.SEND_TO_VOICEMAIL, 1);
values.put(ContactsContract.RawContacts.AGGREGATION_MODE,
RawContacts.AGGREGATION_MODE_IMMEDIATE);
- values.put(ContactsContract.RawContacts.STARRED, 1);
assertEquals(1, mResolver.update(uri, values, null, null));
assertEquals(version, getVersion(uri));
@@ -7872,6 +7882,11 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
assertStoredValue(contactUri, Contacts.STARRED, "0");
+ assertDirty(rawContactUri1, true);
+ assertDirty(rawContactUri2, true);
+ clearDirty(rawContactUri1);
+ clearDirty(rawContactUri2);
+
ContentValues values = new ContentValues();
values.put(RawContacts.STARRED, "1");
@@ -7880,20 +7895,41 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertStoredValue(rawContactUri1, RawContacts.STARRED, "1");
assertStoredValue(rawContactUri2, RawContacts.STARRED, "0");
assertStoredValue(contactUri, Contacts.STARRED, "1");
+ assertDirty(rawContactUri1, true);
+ assertNetworkNotified(true);
+ clearDirty(rawContactUri1);
values.put(RawContacts.STARRED, "0");
mResolver.update(rawContactUri1, values, null, null);
assertStoredValue(rawContactUri1, RawContacts.STARRED, "0");
assertStoredValue(rawContactUri2, RawContacts.STARRED, "0");
assertStoredValue(contactUri, Contacts.STARRED, "0");
+ assertDirty(rawContactUri1, true);
+ assertNetworkNotified(true);
+ clearDirty(rawContactUri1);
values.put(Contacts.STARRED, "1");
mResolver.update(contactUri, values, null, null);
assertStoredValue(rawContactUri1, RawContacts.STARRED, "1");
assertStoredValue(rawContactUri2, RawContacts.STARRED, "1");
assertStoredValue(contactUri, Contacts.STARRED, "1");
+ assertDirty(rawContactUri1, true);
+ assertNetworkNotified(true);
+ }
+
+ public void testUpdateContactOptionsSetStarred() {
+ long rawContactId = RawContactUtil.createRawContact(mResolver);
+ long contactId = queryContactId(rawContactId);
+ String lookupKey = queryLookupKey(contactId);
+ ContentValues values =new ContentValues();
+ values.put(Contacts.STARRED, 1);
+
+ Uri contactLookupUri = ContentUris.withAppendedId(
+ Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
+ mResolver.update(contactLookupUri, values, null, null);
+ assertNetworkNotified(true);
}
public void testSetAndClearSuperPrimaryEmail() {
@@ -9884,6 +9920,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
values.put(Data.DATA14, "fourteen");
values.put(Data.DATA15, "fifteen".getBytes());
values.put(Data.CARRIER_PRESENCE, Data.CARRIER_PRESENCE_VT_CAPABLE);
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME, "preferredcomponentname");
+ values.put(Data.PREFERRED_PHONE_ACCOUNT_ID, "preferredid");
values.put(Data.SYNC1, "sync1");
values.put(Data.SYNC2, "sync2");
values.put(Data.SYNC3, "sync3");
diff --git a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
index 4fa935fa..d20b9b3c 100644
--- a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
+++ b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
@@ -16,11 +16,15 @@
package com.android.providers.contacts;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
import android.content.ContentUris;
import android.content.ContentValues;
+import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
-import android.os.BatteryStats.Uid.Proc;
+import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.provider.CallLog;
@@ -33,6 +37,8 @@ import android.test.suitebuilder.annotation.SmallTest;
import com.android.common.io.MoreCloseables;
+import org.mockito.Mockito;
+
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -51,7 +57,12 @@ import java.util.List;
// TODO: Test that calltype and voicemail_uri are auto populated by the provider.
@SmallTest
public class VoicemailProviderTest extends BaseVoicemailProviderTest {
- /** Fields specific to call_log provider that should not be exposed by voicemail provider. */
+
+ private static final String SYSTEM_PROPERTY_DEXMAKER_DEXCACHE = "dexmaker.dexcache";
+
+ /**
+ * Fields specific to call_log provider that should not be exposed by voicemail provider.
+ */
private static final String[] CALLLOG_PROVIDER_SPECIFIC_COLUMNS = {
Calls.CACHED_NAME,
Calls.CACHED_NUMBER_LABEL,
@@ -60,23 +71,38 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
Calls.VOICEMAIL_URI,
Calls.COUNTRY_ISO
};
- /** Total number of columns exposed by voicemail provider. */
- private static final int NUM_VOICEMAIL_FIELDS = 24;
+ /**
+ * Total number of columns exposed by voicemail provider.
+ */
+ private static final int NUM_VOICEMAIL_FIELDS = 25;
@Override
protected void setUp() throws Exception {
super.setUp();
setUpForOwnPermission();
+ System.setProperty(SYSTEM_PROPERTY_DEXMAKER_DEXCACHE, getContext().getCacheDir().getPath());
+ Thread.currentThread()
+ .setContextClassLoader(VoicemailContentProvider.class.getClassLoader());
addProvider(CallLogProviderTestable.class, CallLog.AUTHORITY);
}
- /** Returns the appropriate /voicemail URI. */
+ @Override
+ protected void tearDown() throws Exception {
+ System.clearProperty(SYSTEM_PROPERTY_DEXMAKER_DEXCACHE);
+ DbModifierWithNotification.setVoicemailNotifierForTest(null);
+ }
+
+ /**
+ * Returns the appropriate /voicemail URI.
+ */
private Uri voicemailUri() {
return mUseSourceUri ?
Voicemails.buildSourceUri(mActor.packageName) : Voicemails.CONTENT_URI;
}
- /** Returns the appropriate /status URI. */
+ /**
+ * Returns the appropriate /status URI.
+ */
private Uri statusUri() {
return mUseSourceUri ?
Status.buildSourceUri(mActor.packageName) : Status.CONTENT_URI;
@@ -96,14 +122,16 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
public void testInsertReadMessageIsNotNew() throws Exception {
ContentValues values = getTestReadVoicemailValues();
+ values.remove(Voicemails.NEW);
Uri uri = mResolver.insert(voicemailUri(), values);
String[] projection = {Voicemails.NUMBER, Voicemails.DATE, Voicemails.DURATION,
- Voicemails.TRANSCRIPTION, Voicemails.IS_READ, Voicemails.HAS_CONTENT,
+ Voicemails.TRANSCRIPTION, Voicemails.NEW, Voicemails.IS_READ,
+ Voicemails.HAS_CONTENT,
Voicemails.SOURCE_DATA, Voicemails.STATE,
Voicemails.BACKED_UP, Voicemails.RESTORED, Voicemails.ARCHIVED,
Voicemails.IS_OMTP_VOICEMAIL
};
- Cursor c = mResolver.query(uri, projection, Calls.NEW + "=0", null,
+ Cursor c = mResolver.query(uri, projection, Voicemails.NEW + "=0", null,
null);
try {
assertEquals("Record count", 1, c.getCount());
@@ -118,6 +146,14 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
}
}
+ public void testBulkInsert() {
+ VoicemailNotifier notifier = mock(VoicemailNotifier.class);
+ DbModifierWithNotification.setVoicemailNotifierForTest(notifier);
+ mResolver.bulkInsert(voicemailUri(),
+ new ContentValues[] {getTestVoicemailValues(), getTestVoicemailValues()});
+ verify(notifier, Mockito.times(1)).sendNotification();
+ }
+
// Test to ensure that media content can be written and read back.
public void testFileContent() throws Exception {
Uri uri = insertVoicemail();
@@ -161,26 +197,97 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
public void testUpdateOwnPackageVoicemail_NotDirty() {
final Uri uri = mResolver.insert(voicemailUri(), getTestVoicemailValues());
- mResolver.update(uri, new ContentValues(), null, null);
+ ContentValues updateValues = new ContentValues();
+ updateValues.put(Voicemails.TRANSCRIPTION, "foo");
+ mResolver.update(uri, updateValues, null, null);
// Updating a package's own voicemail should not make the voicemail dirty.
- ContentValues values = getTestVoicemailValues();
- values.put(Voicemails.DIRTY, "0");
- assertStoredValues(uri, values);
+ try (Cursor cursor = mResolver
+ .query(uri, new String[] {Voicemails.DIRTY}, null, null, null)) {
+ cursor.moveToFirst();
+ assertEquals(cursor.getInt(0), 0);
+ }
+ }
+
+ public void testUpdateOtherPackageCallLog_NotDirty() {
+ setUpForFullPermission();
+ final Uri uri = insertVoicemailForSourcePackage("another-package");
+ // Clear the mapping for our own UID so that this doesn't look like an internal transaction.
+ mPackageManager.removePackage(Process.myUid());
+
+ ContentValues values = new ContentValues();
+ values.put(Calls.CACHED_NAME, "foo");
+ mResolver.update(ContentUris
+ .withAppendedId(CallLog.Calls.CONTENT_URI, ContentUris.parseId(uri)),
+ values, null, null);
+
+ try (Cursor cursor = mResolver
+ .query(uri, new String[] {Voicemails.DIRTY}, null, null, null)) {
+ cursor.moveToFirst();
+ assertEquals(cursor.getInt(0), 0);
+ }
}
public void testUpdateOwnPackageVoicemail_RemovesDirtyStatus() {
ContentValues values = getTestVoicemailValues();
values.put(Voicemails.DIRTY, "1");
- final Uri uri = mResolver.insert(voicemailUri(), getTestVoicemailValues());
-
- mResolver.update(uri, new ContentValues(), null, null);
+ final Uri uri = mResolver.insert(voicemailUri(), values);
+ ContentValues updateValues = new ContentValues();
+ updateValues.put(Voicemails.IS_READ, 1);
+ mResolver.update(uri, updateValues, null, null);
// At this point, the voicemail should be set back to not dirty.
ContentValues newValues = getTestVoicemailValues();
+ newValues.put(Voicemails.IS_READ, 1);
newValues.put(Voicemails.DIRTY, "0");
assertStoredValues(uri, newValues);
}
+ public void testUpdateOwnPackageVoicemail_retainDirtyStatus_dirty() {
+ ContentValues values = getTestVoicemailValues();
+ values.put(Voicemails.DIRTY, "1");
+ final Uri uri = mResolver.insert(voicemailUri(), values);
+
+ ContentValues retainDirty = new ContentValues();
+ retainDirty.put(Voicemails.TRANSCRIPTION, "foo");
+ retainDirty.put(Voicemails.DIRTY, Voicemails.DIRTY_RETAIN);
+
+ mResolver.update(uri, retainDirty, null, null);
+ ContentValues newValues = getTestVoicemailValues();
+ newValues.put(Voicemails.DIRTY, "1");
+ newValues.put(Voicemails.TRANSCRIPTION, "foo");
+ assertStoredValues(uri, newValues);
+ }
+
+ public void testUpdateOwnPackageVoicemail_retainDirtyStatus_notDirty() {
+ ContentValues values = getTestVoicemailValues();
+ values.put(Voicemails.DIRTY, "0");
+ final Uri uri = mResolver.insert(voicemailUri(), values);
+
+ ContentValues retainDirty = new ContentValues();
+ retainDirty.put(Voicemails.TRANSCRIPTION, "foo");
+ retainDirty.put(Voicemails.DIRTY, Voicemails.DIRTY_RETAIN);
+
+ mResolver.update(uri, retainDirty, null, null);
+ ContentValues newValues = getTestVoicemailValues();
+ newValues.put(Voicemails.DIRTY, "0");
+ newValues.put(Voicemails.TRANSCRIPTION, "foo");
+ assertStoredValues(uri, newValues);
+ }
+
+ public void testUpdateOwnPackageVoicemail_retainDirtyStatus_noOtherValues() {
+ ContentValues values = getTestVoicemailValues();
+ values.put(Voicemails.DIRTY, "1");
+ final Uri uri = mResolver.insert(voicemailUri(), values);
+
+ ContentValues retainDirty = new ContentValues();
+ retainDirty.put(Voicemails.DIRTY, Voicemails.DIRTY_RETAIN);
+
+ mResolver.update(uri, retainDirty, null, null);
+ ContentValues newValues = getTestVoicemailValues();
+ newValues.put(Voicemails.DIRTY, "1");
+ assertStoredValues(uri, newValues);
+ }
+
public void testDeleteOwnPackageVoicemail_DeletesRow() {
setUpForFullPermission();
final Uri ownVoicemail = insertVoicemail();
@@ -261,6 +368,12 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
// no package URI specified).
public void testMustUsePackageUriWithoutFullPermission() {
setUpForOwnPermission();
+ assertBaseUriThrowsSecurityExceptions();
+ setUpForOwnPermissionViaCarrierPrivileges();
+ assertBaseUriThrowsSecurityExceptions();
+ }
+
+ private void assertBaseUriThrowsSecurityExceptions() {
EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
@Override
public void run() {
@@ -299,24 +412,31 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
// Now give away full permission and check that only 1 message is accessible.
setUpForOwnPermission();
- assertEquals(1, getCount(voicemailUri(), null, null));
+ assertOnlyOwnVoicemailsCanBeQueriedAndInserted();
+ // Same as above, but with carrier privileges.
+ setUpForOwnPermissionViaCarrierPrivileges();
+ assertOnlyOwnVoicemailsCanBeQueriedAndInserted();
- // Once again try to insert message for another package. This time
- // it should fail.
+ setUpForNoPermission();
+ mUseSourceUri = false;
+ // With the READ_ALL_VOICEMAIL permission, we should now be able to read all voicemails
+ mActor.addPermissions(READ_VOICEMAIL_PERMISSION);
+ assertEquals(2, getCount(voicemailUri(), null, null));
+
+ // An insert for another package should still fail
EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
@Override
public void run() {
insertVoicemailForSourcePackage("another-package");
}
});
+ }
- setUpForNoPermission();
- mUseSourceUri = false;
- // With the READ_ALL_VOICEMAIL permission, we should now be able to read all voicemails
- mActor.addPermissions(READ_VOICEMAIL_PERMISSION);
- assertEquals(2, getCount(voicemailUri(), null, null));
+ private void assertOnlyOwnVoicemailsCanBeQueriedAndInserted() {
+ assertEquals(1, getCount(voicemailUri(), null, null));
- // An insert for another package should still fail
+ // Once again try to insert message for another package. This time
+ // it should fail.
EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
@Override
public void run() {
@@ -335,23 +455,9 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
// Now give away full permission and check that we can update and delete only
// the own voicemail.
setUpForOwnPermission();
- mResolver.update(withSourcePackageParam(ownVoicemail),
- getTestVoicemailValues(), null, null);
- mResolver.delete(withSourcePackageParam(ownVoicemail), null, null);
-
- // However, attempting to update or delete another-package's voicemail should fail.
- EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
- @Override
- public void run() {
- mResolver.update(anotherVoicemail, null, null, null);
- }
- });
- EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
- @Override
- public void run() {
- mResolver.delete(anotherVoicemail, null, null);
- }
- });
+ assertOnlyOwnVoicemailsCanBeUpdatedAndDeleted(ownVoicemail, anotherVoicemail);
+ setUpForOwnPermissionViaCarrierPrivileges();
+ assertOnlyOwnVoicemailsCanBeUpdatedAndDeleted(ownVoicemail, anotherVoicemail);
// If we have the manage voicemail permission, we should be able to both update voicemails
// from all packages.
@@ -374,10 +480,32 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
assertEquals(0, getCount(anotherVoicemail, null, null));
}
+ private void assertOnlyOwnVoicemailsCanBeUpdatedAndDeleted(
+ Uri ownVoicemail, Uri anotherVoicemail) {
+ mResolver.update(withSourcePackageParam(ownVoicemail),
+ getTestVoicemailValues(), null, null);
+ mResolver.delete(withSourcePackageParam(ownVoicemail), null, null);
+
+ // However, attempting to update or delete another-package's voicemail should fail.
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.update(anotherVoicemail, null, null, null);
+ }
+ });
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.delete(anotherVoicemail, null, null);
+ }
+ });
+ }
+
private Uri withSourcePackageParam(Uri uri) {
return uri.buildUpon()
- .appendQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE, mActor.packageName)
- .build();
+ .appendQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE,
+ mActor.packageName)
+ .build();
}
public void testUriPermissions() {
@@ -440,7 +568,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
private void checkHasReadAccessToUri(final Uri uri) {
Cursor cursor = null;
try {
- cursor = mResolver.query(uri, null, null ,null, null);
+ cursor = mResolver.query(uri, null, null, null, null);
assertEquals(1, cursor.getCount());
try {
ParcelFileDescriptor fd = mResolver.openFileDescriptor(uri, "r");
@@ -552,11 +680,11 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
values.put(callLogColumn, "foo");
EvenMoreAsserts.assertThrows("Column: " + callLogColumn,
IllegalArgumentException.class, new Runnable() {
- @Override
- public void run() {
- mResolver.insert(voicemailUri(), values);
- }
- });
+ @Override
+ public void run() {
+ mResolver.insert(voicemailUri(), values);
+ }
+ });
}
}
@@ -583,11 +711,11 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
values.put(callLogColumn, "foo");
EvenMoreAsserts.assertThrows("Column: " + callLogColumn,
IllegalArgumentException.class, new Runnable() {
- @Override
- public void run() {
- mResolver.update(insertedUri, values, null, null);
- }
- });
+ @Override
+ public void run() {
+ mResolver.update(insertedUri, values, null, null);
+ }
+ });
}
}
@@ -613,6 +741,28 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
assertStoredValues(uri, values);
}
+ public void testStatusUpdate_observerNotified() throws Exception {
+ Uri uri = insertTestStatusEntry();
+ ContentValues values = getTestStatusValues();
+ values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_NO_CONNECTION);
+ values.put(Status.NOTIFICATION_CHANNEL_STATE,
+ Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
+ values.put(Status.SOURCE_TYPE,
+ "vvm_type_test2");
+ Boolean[] observerTriggered = new Boolean[]{false};
+ mResolver.registerContentObserver(Status.CONTENT_URI, true,
+ new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ observerTriggered[0] = true;
+ }
+ });
+
+ mResolver.update(uri, values, null, null);
+
+ assertTrue(observerTriggered[0]);
+ }
+
public void testStatusUpsert() throws Exception {
ContentValues values = getTestStatusValues();
mResolver.insert(statusUri(), values);
@@ -751,7 +901,9 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
return mResolver.insert(voicemailUri(), getTestVoicemailValues());
}
- /** Inserts a voicemail record for the specified source package. */
+ /**
+ * Inserts a voicemail record for the specified source package.
+ */
private Uri insertVoicemailForSourcePackage(String sourcePackage) {
ContentValues values = getTestVoicemailValues();
values.put(Voicemails.SOURCE_PACKAGE, sourcePackage);
@@ -763,6 +915,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest {
values.put(Voicemails.NUMBER, "1-800-4664-411");
values.put(Voicemails.DATE, 1000);
values.put(Voicemails.DURATION, 30);
+ values.put(Voicemails.NEW, 0);
values.put(Voicemails.TRANSCRIPTION, "Testing 123");
values.put(Voicemails.IS_READ, 0);
values.put(Voicemails.HAS_CONTENT, 0);
diff --git a/tests/src/com/android/providers/contacts/util/CappedStringBuilderTest.java b/tests/src/com/android/providers/contacts/util/CappedStringBuilderTest.java
new file mode 100644
index 00000000..d49b2630
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/util/CappedStringBuilderTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 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.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Run with:
+ atest /android/pi-dev/packages/providers/ContactsProvider/tests/src/com/android/providers/contacts/util/CappedStringBuilderTest.java
+ */
+@SmallTest
+public class CappedStringBuilderTest extends TestCase {
+ public void testCappedChar() {
+ CappedStringBuilder csb = new CappedStringBuilder(8);
+
+ csb.append("abcd");
+ csb.append("efgh");
+
+ csb.append('x');
+ assertEquals("abcdefgh", csb.toString());
+
+ csb.append("y");
+ csb.append("yz");
+
+ assertEquals("abcdefgh", csb.toString());
+ }
+
+ public void testCappedString() {
+ CappedStringBuilder csb = new CappedStringBuilder(8);
+
+ csb.append("abcd");
+ csb.append("efgh");
+
+ csb.append("x");
+ assertEquals("abcdefgh", csb.toString());
+ }
+
+ public void testClear() {
+ CappedStringBuilder csb = new CappedStringBuilder(8);
+
+ csb.append("abcd");
+ csb.append("efgh");
+
+ csb.append("x");
+
+ assertEquals("abcdefgh", csb.toString());
+
+ csb.clear();
+
+ assertEquals("", csb.toString());
+
+ csb.append("abcd");
+ assertEquals("abcd", csb.toString());
+ }
+
+ public void testAlreadyCapped() {
+ CappedStringBuilder csb = new CappedStringBuilder(4);
+
+ csb.append("abc");
+
+ csb.append("xy");
+
+ // Once capped, further append() will all be blocked.
+ csb.append('z');
+ csb.append("z");
+
+ assertEquals("abc", csb.toString());
+ }
+}
diff --git a/tests2/Android.mk b/tests2/Android.mk
index 2662cfd1..4ba0a12f 100644
--- a/tests2/Android.mk
+++ b/tests2/Android.mk
@@ -22,10 +22,9 @@ LOCAL_MODULE_TAGS := tests
LOCAL_STATIC_JAVA_LIBRARIES := \
ContactsProviderTestUtils \
android-support-test \
- mockito-target-minus-junit4 \
- legacy-android-test
+ mockito-target-minus-junit4
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests2/AndroidTest.xml b/tests2/AndroidTest.xml
index 9f541e5d..29fdc83f 100644
--- a/tests2/AndroidTest.xml
+++ b/tests2/AndroidTest.xml
@@ -23,5 +23,6 @@
<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" />
+ <option name="hidden-api-checks" value="false"/>
</test>
</configuration>