Introduced new voicemail fields in 'calls' table.
The 'calls' table is going to be shared between the existing call_log
provider, and to be added voicemail provider. This change adds all the
columns needed to support voicemail in the 'calls' table.
The call_log provider, however, uses only one additional field
'voicemail_uri', whereas all other new fields will exclusively be used the
new voicemail provider. The change also ensures that the voicemail
provider specific fields are not exposed through the call_log provider.
Change-Id: Ieea4b14052b7e7e9db0e674138772b4e06b3f074
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index ae43c88..689e6be 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -32,6 +32,7 @@
import android.provider.CallLog.Calls;
import java.util.HashMap;
+import java.util.Set;
/**
* Call log content provider.
@@ -62,6 +63,7 @@
sCallsProjectionMap.put(Calls.DURATION, Calls.DURATION);
sCallsProjectionMap.put(Calls.TYPE, Calls.TYPE);
sCallsProjectionMap.put(Calls.NEW, Calls.NEW);
+ sCallsProjectionMap.put(Calls.VOICEMAIL_URI, Calls.VOICEMAIL_URI);
sCallsProjectionMap.put(Calls.CACHED_NAME, Calls.CACHED_NAME);
sCallsProjectionMap.put(Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_TYPE);
sCallsProjectionMap.put(Calls.CACHED_NUMBER_LABEL, Calls.CACHED_NUMBER_LABEL);
@@ -93,25 +95,26 @@
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(Tables.CALLS);
+ qb.setProjectionMap(sCallsProjectionMap);
+ qb.setStrict(true);
+
int match = sURIMatcher.match(uri);
switch (match) {
- case CALLS: {
- qb.setTables("calls");
- qb.setProjectionMap(sCallsProjectionMap);
+ case CALLS:
break;
- }
case CALLS_ID: {
- qb.setTables("calls");
- qb.setProjectionMap(sCallsProjectionMap);
- qb.appendWhere("calls._id=");
- qb.appendWhere(uri.getPathSegments().get(1));
+ try {
+ Long id = Long.valueOf(uri.getPathSegments().get(1));
+ qb.appendWhere(Calls._ID + "=" + id.toString());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid call id in uri: " + uri, e);
+ }
break;
}
case CALLS_FILTER: {
- qb.setTables("calls");
- qb.setProjectionMap(sCallsProjectionMap);
String phoneNumber = uri.getPathSegments().get(2);
qb.appendWhere("PHONE_NUMBERS_EQUAL(number, ");
qb.appendWhereEscapeString(phoneNumber);
@@ -148,6 +151,7 @@
@Override
public Uri insert(Uri uri, ContentValues values) {
+ checkForSupportedColumns(values);
// Inserted the current country code, so we know the country
// the number belongs to.
values.put(Calls.COUNTRY_ISO, getCurrentCountryIso());
@@ -166,6 +170,7 @@
@Override
public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
+ checkForSupportedColumns(values);
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
String where;
final int matchedUriId = sURIMatcher.match(url);
@@ -216,4 +221,13 @@
protected String getCurrentCountryIso() {
return mCountryMonitor.getCountryIso();
}
+
+ /** Checks if ContentValues contains none other than supported columns. */
+ private void checkForSupportedColumns(ContentValues values) {
+ for (String requestedColumn : values.keySet()) {
+ if (!sCallsProjectionMap.keySet().contains(requestedColumn)) {
+ throw new IllegalArgumentException("Column '" + requestedColumn + "' is invalid.");
+ }
+ }
+ }
}
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 473b30a..b14f422 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -65,6 +65,7 @@
import android.provider.ContactsContract.Settings;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.SocialContract.Activities;
+import android.provider.VoicemailContract.Voicemails;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.text.util.Rfc822Token;
@@ -96,7 +97,7 @@
* 600-699 Ice Cream Sandwich
* </pre>
*/
- static final int DATABASE_VERSION = 601;
+ static final int DATABASE_VERSION = 602;
private static final String DATABASE_NAME = "contacts2.db";
private static final String DATABASE_PRESENCE = "presence_db";
@@ -1065,7 +1066,15 @@
Calls.CACHED_NAME + " TEXT," +
Calls.CACHED_NUMBER_TYPE + " INTEGER," +
Calls.CACHED_NUMBER_LABEL + " TEXT," +
- Calls.COUNTRY_ISO + " TEXT" + ");");
+ Calls.COUNTRY_ISO + " TEXT," +
+ Calls.VOICEMAIL_URI + " TEXT," +
+ Voicemails._DATA + " TEXT," +
+ Voicemails.HAS_CONTENT + " INTEGER," +
+ Voicemails.MIME_TYPE + " TEXT," +
+ Voicemails.SOURCE_DATA + " TEXT," +
+ Voicemails.SOURCE_PACKAGE + " TEXT," +
+ Voicemails.STATE + " INTEGER" +
+ ");");
// Activities table
db.execSQL("CREATE TABLE " + Tables.ACTIVITIES + " (" +
@@ -1922,6 +1931,11 @@
oldVersion = 601;
}
+ if (oldVersion < 602) {
+ upgradeToVersion602(db);
+ oldVersion = 602;
+ }
+
if (upgradeViewsAndTriggers) {
createContactsViews(db);
createGroupsView(db);
@@ -2986,6 +3000,16 @@
"data_usage_stat (data_id, usage_type)");
}
+ private void upgradeToVersion602(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE calls ADD voicemail_uri TEXT;");
+ db.execSQL("ALTER TABLE calls ADD _data TEXT;");
+ db.execSQL("ALTER TABLE calls ADD has_content INTEGER;");
+ db.execSQL("ALTER TABLE calls ADD mime_type TEXT;");
+ db.execSQL("ALTER TABLE calls ADD source_data TEXT;");
+ db.execSQL("ALTER TABLE calls ADD source_package TEXT;");
+ db.execSQL("ALTER TABLE calls ADD state INTEGER;");
+ }
+
public String extractHandleFromEmailAddress(String email) {
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email);
if (tokens.length == 0) {
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 5da4950..8ac0b26 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -683,6 +683,7 @@
}
protected static class IdComparator implements Comparator<ContentValues> {
+ @Override
public int compare(ContentValues o1, ContentValues o2) {
long id1 = o1.getAsLong(ContactsContract.Data._ID);
long id2 = o2.getAsLong(ContactsContract.Data._ID);
diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
index 392fa65..bf79e8b 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -18,17 +18,23 @@
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.Connection;
+import com.android.providers.contacts.EvenMoreAsserts;
+
+import java.util.Arrays;
+import java.util.List;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.CallLog;
import android.provider.ContactsContract;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.VoicemailContract.Voicemails;
import android.test.suitebuilder.annotation.MediumTest;
/**
@@ -42,6 +48,13 @@
*/
@MediumTest
public class CallLogProviderTest extends BaseContactsProvider2Test {
+ private static final String[] VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS = new String[] {
+ Voicemails._DATA,
+ Voicemails.HAS_CONTENT,
+ Voicemails.MIME_TYPE,
+ Voicemails.SOURCE_PACKAGE,
+ Voicemails.SOURCE_DATA,
+ Voicemails.STATE};
@Override
protected Class<? extends ContentProvider> getProviderClass() {
@@ -59,7 +72,7 @@
addProvider(TestCallLogProvider.class, CallLog.AUTHORITY);
}
- public void testInsert() {
+ public void testInsert_RegularCallRecord() {
ContentValues values = new ContentValues();
putCallValues(values);
Uri uri = mResolver.insert(Calls.CONTENT_URI, values);
@@ -68,12 +81,19 @@
assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri));
}
- public void testUpdate() {
+ public void testInsert_VoicemailCallRecord() {
ContentValues values = new ContentValues();
putCallValues(values);
- Uri uri = mResolver.insert(Calls.CONTENT_URI, values);
+ values.put(Calls.TYPE, Calls.VOICEMAIL_TYPE);
+ values.put(Calls.VOICEMAIL_URI, "content://foo/voicemail/2");
+ Uri uri = mResolver.insert(Calls.CONTENT_URI, values);
+ assertStoredValues(uri, values);
+ assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri));
+ }
- values.clear();
+ public void testUpdate() {
+ Uri uri = insertCallRecord();
+ ContentValues values = new ContentValues();
values.put(Calls.TYPE, Calls.OUTGOING_TYPE);
values.put(Calls.NUMBER, "1-800-263-7643");
values.put(Calls.DATE, 2000);
@@ -88,9 +108,7 @@
}
public void testDelete() {
- ContentValues values = new ContentValues();
- putCallValues(values);
- Uri uri = mResolver.insert(Calls.CONTENT_URI, values);
+ Uri uri = insertCallRecord();
try {
mResolver.delete(uri, null, null);
fail();
@@ -142,6 +160,54 @@
assertStoredValues(uri, values);
}
+ // Test to check that none of the voicemail provider specific fields are
+ // insertable through call_log provider.
+ public void testCannotAccessVoicemailSpecificFields_Insert() {
+ for (String voicemailColumn : VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS) {
+ final ContentValues values = new ContentValues();
+ putCallValues(values);
+ values.put(voicemailColumn, "foo");
+ EvenMoreAsserts.assertThrows("Column: " + voicemailColumn,
+ IllegalArgumentException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.insert(Calls.CONTENT_URI, values);
+ }
+ });
+ }
+ }
+
+ // Test to check that none of the voicemail provider specific fields are
+ // exposed through call_log provider query.
+ public void testCannotAccessVoicemailSpecificFields_Query() {
+ // Query.
+ Cursor cursor = mResolver.query(Calls.CONTENT_URI, null, null, null, null);
+ List<String> columnNames = Arrays.asList(cursor.getColumnNames());
+ assertEquals(11, columnNames.size());
+ // None of the voicemail provider specific columns should be present.
+ for (String voicemailColumn : VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS) {
+ assertFalse("Unexpected column: '" + voicemailColumn + "' returned.",
+ columnNames.contains(voicemailColumn));
+ }
+ }
+
+ // Test to check that none of the voicemail provider specific fields are
+ // updatable through call_log provider.
+ public void testCannotAccessVoicemailSpecificFields_Update() {
+ for (String voicemailColumn : VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS) {
+ final Uri insertedUri = insertCallRecord();
+ final ContentValues values = new ContentValues();
+ values.put(voicemailColumn, "foo");
+ EvenMoreAsserts.assertThrows("Column: " + voicemailColumn,
+ IllegalArgumentException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.update(insertedUri, values, null, null);
+ }
+ });
+ }
+ }
+
private void putCallValues(ContentValues values) {
values.put(Calls.TYPE, Calls.INCOMING_TYPE);
values.put(Calls.NUMBER, "1-800-4664-411");
@@ -150,6 +216,12 @@
values.put(Calls.NEW, 1);
}
+ private Uri insertCallRecord() {
+ ContentValues values = new ContentValues();
+ putCallValues(values);
+ return mResolver.insert(Calls.CONTENT_URI, values);
+ }
+
public static class TestCallLogProvider extends CallLogProvider {
private static ContactsDatabaseHelper mDbHelper;
diff --git a/tests/src/com/android/providers/contacts/EvenMoreAsserts.java b/tests/src/com/android/providers/contacts/EvenMoreAsserts.java
new file mode 100644
index 0000000..aac8a67
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/EvenMoreAsserts.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.contacts;
+
+import junit.framework.Assert;
+
+/**
+ * Contains additional assertion methods not found in Junit or MoreAsserts.
+ */
+public final class EvenMoreAsserts {
+ // Non instantiable.
+ private EvenMoreAsserts() { }
+
+ public static <T extends Exception> void assertThrows(Class<T> exception, Runnable r) {
+ assertThrows(null, exception, r);
+ }
+
+ public static <T extends Exception> void assertThrows(String message, Class<T> exception,
+ Runnable r) {
+ try {
+ r.run();
+ // Cannot invoke Assert.fail() here because it will be caught by the try/catch below
+ // and, if we are expecting an AssertionError or AssertionFailedError (depending on
+ // the platform), we might incorrectly identify that as a success.
+ } catch (Exception e) {
+ if (!exception.isInstance(e)) {
+ Assert.fail(appendUserMessage("Exception " + exception + " expected but " +
+ e.getClass() +" thrown: " + e, message));
+ }
+ return;
+ }
+ Assert.fail(appendUserMessage(
+ "Exception " + exception + " expected but no exception was thrown.",
+ message));
+ }
+
+ private static String appendUserMessage(String errorMsg, String userMsg) {
+ return userMsg == null ? errorMsg : errorMsg + userMsg;
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java b/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
index 612808f..f4b8bab 100644
--- a/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
+++ b/tests/src/com/android/providers/contacts/SqlInjectionDetectionTest.java
@@ -16,6 +16,8 @@
package com.android.providers.contacts;
+import static com.android.providers.contacts.EvenMoreAsserts.assertThrows;
+
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
@@ -108,18 +110,4 @@
}
});
}
-
- private static <T extends Exception> void assertThrows(Class<T> exception, Runnable r) {
- try {
- r.run();
- Assert.fail("Exception " + exception + " expected but no exception was thrown");
- } catch (Exception e) {
- if (exception.isInstance(e)) {
- return;
- }
- Assert.fail("Exception " + exception + " expected but " + e.getClass() +" thrown: " +
- e);
- }
- }
-
}