summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDebashish Chatterjee <debashishc@google.com>2011-06-20 17:03:26 +0100
committerDebashish Chatterjee <debashishc@google.com>2011-06-28 15:04:46 +0100
commitaafbe295d67686870c64c74a59e589d1dfb506fa (patch)
treef413f2484f7cb4e1fe534ee35ba456deb150aad6
parent836bbd3dc178f94bb674733b8ad57c765fff311a (diff)
downloadpackages_providers_ContactsProvider-aafbe295d67686870c64c74a59e589d1dfb506fa.zip
packages_providers_ContactsProvider-aafbe295d67686870c64c74a59e589d1dfb506fa.tar.gz
packages_providers_ContactsProvider-aafbe295d67686870c64c74a59e589d1dfb506fa.tar.bz2
Introduced query param 'include_voicemails' for call_log uri.
- by default only call entries (i.e. no voicemails) are returned. - if include_voicemails is set to true then also include voicemail records, but only if the caller has full voicemail permission. - voicemail record can only be inserted through call_log provider if include_voicemails is set. Change-Id: I98f6778ace64fa752dc0525c5ce4e5eb83b2e689
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java137
-rw-r--r--src/com/android/providers/contacts/VoicemailContentProvider.java60
-rw-r--r--src/com/android/providers/contacts/VoicemailPermissions.java91
-rw-r--r--src/com/android/providers/contacts/util/DbQueryUtils.java11
-rw-r--r--src/com/android/providers/contacts/util/SelectionBuilder.java63
-rw-r--r--tests/src/com/android/providers/contacts/CallLogProviderTest.java133
-rw-r--r--tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java4
-rw-r--r--tests/src/com/android/providers/contacts/util/SelectionBuilderTest.java56
8 files changed, 476 insertions, 79 deletions
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index e7a6351..b07a56d 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -17,9 +17,12 @@
package com.android.providers.contacts;
import static com.android.providers.contacts.util.DbQueryUtils.checkForSupportedColumns;
+import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
+import static com.android.providers.contacts.util.DbQueryUtils.getInequalityClause;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.util.DbQueryUtils;
+import com.android.providers.contacts.util.SelectionBuilder;
import android.content.ContentProvider;
import android.content.ContentUris;
@@ -33,6 +36,8 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
+import android.util.Log;
import java.util.HashMap;
import java.util.Set;
@@ -42,6 +47,31 @@ import java.util.Set;
*/
public class CallLogProvider extends ContentProvider {
+ /**
+ * An optional URI parameter for call_log operations which instructs the
+ * provider to allow the operation to be applied to voicemail records as well.
+ * <p> TYPE: Boolean
+ *
+ * <p> Using this parameter with a value true will result in a security
+ * error if the calling application does not have appropriate permissions
+ * to access voicemails.
+ */
+ // TODO: Move this to ContactsContract.Calls once we are happy with the changes.
+ public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails";
+
+ /**
+ * Content uri with {@link #ALLOW_VOICEMAILS_PARAM_KEY} set. This can directly
+ * be used to access call log entries that includes voicemail records.
+ */
+ // TODO: Move this to ContactsContract.Calls once we are happy with the changes.
+ public static Uri CONTENT_URI_WITH_VOICEMAIL = Calls.CONTENT_URI.buildUpon()
+ .appendQueryParameter(CallLogProvider.ALLOW_VOICEMAILS_PARAM_KEY, "true")
+ .build();
+
+ /** Selection clause to use to exclude voicemail records. */
+ private static final String EXCLUDE_VOICEMAIL_SELECTION = getInequalityClause(
+ Calls.TYPE, Integer.toString(Calls.VOICEMAIL_TYPE));
+
private static final int CALLS = 1;
private static final int CALLS_ID = 2;
@@ -77,6 +107,7 @@ public class CallLogProvider extends ContentProvider {
private DatabaseUtils.InsertHelper mCallsInserter;
private boolean mUseStrictPhoneNumberComparation;
private CountryMonitor mCountryMonitor;
+ private VoicemailPermissions mVoicemailPermissions;
@Override
public boolean onCreate() {
@@ -86,6 +117,7 @@ public class CallLogProvider extends ContentProvider {
context.getResources().getBoolean(
com.android.internal.R.bool.config_use_strict_phone_number_comparation);
mCountryMonitor = new CountryMonitor(context);
+ mVoicemailPermissions = new VoicemailPermissions(context);
return true;
}
@@ -102,18 +134,17 @@ public class CallLogProvider extends ContentProvider {
qb.setProjectionMap(sCallsProjectionMap);
qb.setStrict(true);
+ SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+ checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
+
int match = sURIMatcher.match(uri);
switch (match) {
case CALLS:
break;
case CALLS_ID: {
- 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);
- }
+ selectionBuilder.addClause(getEqualityClause(Calls._ID,
+ parseCallIdFromUri(uri)));
break;
}
@@ -130,7 +161,8 @@ public class CallLogProvider extends ContentProvider {
}
final SQLiteDatabase db = mDbHelper.getReadableDatabase();
- Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder, null);
+ Cursor c = qb.query(db, projection, selectionBuilder.build(), selectionArgs, null, null,
+ sortOrder, null);
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), CallLog.CONTENT_URI);
}
@@ -155,6 +187,13 @@ public class CallLogProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
checkForSupportedColumns(sCallsProjectionMap, values);
+ // Inserting a voicemail record through call_log requires the voicemail
+ // permission and also requires the additional voicemail param set.
+ if (hasVoicemailValue(values)) {
+ checkIsAllowVoicemailRequest(uri);
+ mVoicemailPermissions.checkCallerHasFullAccess();
+ }
+
// Inserted the current country code, so we know the country
// the number belongs to.
values.put(Calls.COUNTRY_ISO, getCurrentCountryIso());
@@ -172,26 +211,32 @@ public class CallLogProvider extends ContentProvider {
}
@Override
- public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
checkForSupportedColumns(sCallsProjectionMap, values);
+ // Request that involves changing record type to voicemail requires the
+ // voicemail param set in the uri.
+ if (hasVoicemailValue(values)) {
+ checkIsAllowVoicemailRequest(uri);
+ }
+
+ SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+ checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
+
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- String where;
- final int matchedUriId = sURIMatcher.match(url);
+ final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
case CALLS:
- where = selection;
break;
case CALLS_ID:
- where = DatabaseUtils.concatenateWhere(selection, Calls._ID + "="
- + url.getPathSegments().get(1));
+ selectionBuilder.addClause(getEqualityClause(Calls._ID, parseCallIdFromUri(uri)));
break;
default:
- throw new UnsupportedOperationException("Cannot update URL: " + url);
+ throw new UnsupportedOperationException("Cannot update URL: " + uri);
}
- int count = db.update(Tables.CALLS, values, where, selectionArgs);
+ int count = db.update(Tables.CALLS, values, selectionBuilder.build(), selectionArgs);
if (count > 0) {
notifyChange();
}
@@ -200,12 +245,14 @@ public class CallLogProvider extends ContentProvider {
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- final SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+ checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
+ final SQLiteDatabase db = mDbHelper.getWritableDatabase();
final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
case CALLS:
- int count = db.delete(Tables.CALLS, selection, selectionArgs);
+ int count = db.delete(Tables.CALLS, selectionBuilder.build(), selectionArgs);
if (count > 0) {
notifyChange();
}
@@ -224,4 +271,60 @@ public class CallLogProvider extends ContentProvider {
protected String getCurrentCountryIso() {
return mCountryMonitor.getCountryIso();
}
+
+ private boolean hasVoicemailValue(ContentValues values) {
+ return values.containsKey(Calls.TYPE) &&
+ values.getAsInteger(Calls.TYPE).equals(Calls.VOICEMAIL_TYPE);
+ }
+
+ /**
+ * Checks if the supplied uri requests to include voicemails and take appropriate
+ * action.
+ * <p> If voicemail is requested, then check for voicemail permissions. Otherwise
+ * modify the selection to restrict to non-voicemail entries only.
+ */
+ private void checkVoicemailPermissionAndAddRestriction(Uri uri,
+ SelectionBuilder selectionBuilder) {
+ if (isAllowVoicemailRequest(uri)) {
+ mVoicemailPermissions.checkCallerHasFullAccess();
+ } else {
+ selectionBuilder.addClause(EXCLUDE_VOICEMAIL_SELECTION);
+ }
+ }
+
+ /**
+ * Determines if the supplied uri has the request to allow voicemails to be
+ * included.
+ */
+ private boolean isAllowVoicemailRequest(Uri uri) {
+ return uri.getBooleanQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, false);
+ }
+
+ /**
+ * Checks to ensure that the given uri has allow_voicemail set. Used by
+ * insert and update operations to check that ContentValues with voicemail
+ * call type must use the voicemail uri.
+ * @throws IllegalArgumentException if allow_voicemail is not set.
+ */
+ private void checkIsAllowVoicemailRequest(Uri uri) {
+ if (!isAllowVoicemailRequest(uri)) {
+ throw new IllegalArgumentException(
+ String.format("Uri %s cannot be used for voicemail record." +
+ " Please set '%s=true' in the uri.", uri, ALLOW_VOICEMAILS_PARAM_KEY));
+ }
+ }
+
+ /**
+ * Parses the call Id from the given uri, assuming that this is a uri that
+ * matches CALLS_ID. For other uri types the behaviour is undefined.
+ * @throws IllegalArgumentException if the id included in the Uri is not a valid long value.
+ */
+ private String parseCallIdFromUri(Uri uri) {
+ try {
+ Long id = Long.valueOf(uri.getPathSegments().get(1));
+ return id.toString();
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid call id in uri: " + uri, e);
+ }
+ }
}
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index 52903d1..07a9103 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -82,14 +82,14 @@ public class VoicemailContentProvider extends ContentProvider {
.build();
private ContentResolver mContentResolver;
private ContactsDatabaseHelper mDbHelper;
+ private VoicemailPermissions mVoicemailPermissions;
@Override
public boolean onCreate() {
Context context = context();
-
mContentResolver = context.getContentResolver();
mDbHelper = getDatabaseHelper(context);
-
+ mVoicemailPermissions = new VoicemailPermissions(context);
return true;
}
@@ -141,7 +141,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
UriData uriData = createUriData(uri);
checkPackagePermission(uriData);
return queryInternal(uriData, projection,
@@ -184,7 +184,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public int bulkInsert(Uri uri, ContentValues[] valuesArray) {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
// TODO: There is scope to optimize this method further. At the least we can avoid doing the
// extra work related to the calling provider and checking permissions.
UriData uriData = createUriData(uri);
@@ -202,7 +202,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
return insertInternal(createUriData(uri), values, true);
}
@@ -266,7 +266,7 @@ public class VoicemailContentProvider extends ContentProvider {
}
}
// You must have access to the provider given in values.
- if (!callerHasFullPermission()) {
+ if (!mVoicemailPermissions.callerHasFullAccess()) {
checkPackagesMatch(getCallingPackage(), values.getAsString(Voicemails.SOURCE_PACKAGE),
uriData.getUri());
}
@@ -297,7 +297,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
UriData uriData = createUriData(uri);
checkPackagePermission(uriData);
checkForSupportedColumns(sVoicemailProjectionMap, values);
@@ -323,7 +323,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
UriData uriData = createUriData(uri);
checkPackagePermission(uriData);
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
@@ -359,7 +359,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
UriData uriData = createUriData(uri);
checkPackagePermission(uriData);
@@ -463,7 +463,7 @@ public class VoicemailContentProvider extends ContentProvider {
* @throws SecurityException if the check fails.
*/
private void checkPackagePermission(UriData uriData) {
- if (!callerHasFullPermission()) {
+ if (!mVoicemailPermissions.callerHasFullAccess()) {
if (!uriData.hasSourcePackage()) {
// You cannot have a match if this is not a provider uri.
throw new SecurityException(String.format(
@@ -525,11 +525,11 @@ public class VoicemailContentProvider extends ContentProvider {
// which one we return.
String bestSoFar = callerPackages[0];
for (String callerPackage : callerPackages) {
- if (hasPermission(callerPackage, Manifest.permission.READ_WRITE_ALL_VOICEMAIL)) {
+ if (mVoicemailPermissions.packageHasFullAccess(callerPackage)) {
// Full always wins, we can return early.
return callerPackage;
}
- if (hasPermission(callerPackage, Manifest.permission.READ_WRITE_OWN_VOICEMAIL)) {
+ if (mVoicemailPermissions.packageHasOwnVoicemailAccess(callerPackage)) {
bestSoFar = callerPackage;
}
}
@@ -537,45 +537,11 @@ public class VoicemailContentProvider extends ContentProvider {
}
/**
- * This check is made once only at every entry-point into this class from outside.
- *
- * @throws SecurityException if the caller does not have the voicemail source permission.
- */
- private void checkCallerHasOwnPermission() {
- if (!callerHasOwnPermission()) {
- throw new SecurityException("The caller must have permission: " +
- Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
- }
- }
-
- /** Determines if the calling process has own permission. */
- private boolean callerHasOwnPermission() {
- return callerHasPermission(Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
- }
-
- /** Determines if the calling process has full permission. */
- private boolean callerHasFullPermission() {
- return callerHasOwnPermission() &&
- callerHasPermission(Manifest.permission.READ_WRITE_ALL_VOICEMAIL);
- }
-
- /** Determines if the calling process has the given permission. */
- boolean callerHasPermission(String permission) {
- return context().checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
- }
-
- /** Determines if the given package has the given permission. */
- boolean hasPermission(String packageName, String permission) {
- return context().getPackageManager().checkPermission(permission, packageName)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
* Creates a clause to restrict the selection to the calling provider or null if the caller has
* access to all data.
*/
private String getPackageRestrictionClause() {
- if (callerHasFullPermission()) {
+ if (mVoicemailPermissions.callerHasFullAccess()) {
return null;
}
return getEqualityClause(Voicemails.SOURCE_PACKAGE, getCallingPackage());
diff --git a/src/com/android/providers/contacts/VoicemailPermissions.java b/src/com/android/providers/contacts/VoicemailPermissions.java
new file mode 100644
index 0000000..34dbced
--- /dev/null
+++ b/src/com/android/providers/contacts/VoicemailPermissions.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+/**
+ * Provides method related to check various voicemail permissions under the
+ * specified context.
+ * <p> This is an immutable object.
+ */
+public class VoicemailPermissions {
+ private final Context mContext;
+
+ public VoicemailPermissions(Context context) {
+ mContext = context;
+ }
+
+ /** Determines if the calling process has access to its own voicemails. */
+ public boolean callerHasOwnVoicemailAccess() {
+ return callerHasPermission(Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
+ }
+
+ /** Determines if the calling process has access to all voicemails. */
+ public boolean callerHasFullAccess() {
+ return callerHasPermission(Manifest.permission.READ_WRITE_OWN_VOICEMAIL) &&
+ callerHasPermission(Manifest.permission.READ_WRITE_ALL_VOICEMAIL);
+ }
+
+ /**
+ * Checks that the caller has permissions to access its own voicemails.
+ *
+ * @throws SecurityException if the caller does not have the voicemail source permission.
+ */
+ public void checkCallerHasOwnVoicemailAccess() {
+ if (!callerHasOwnVoicemailAccess()) {
+ throw new SecurityException("The caller must have permission: " +
+ Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
+ }
+ }
+
+ /**
+ * Checks that the caller has permissions to access ALL voicemails.
+ *
+ * @throws SecurityException if the caller does not have the voicemail source permission.
+ */
+ public void checkCallerHasFullAccess() {
+ if (!callerHasFullAccess()) {
+ throw new SecurityException(String.format("The caller must have permissions %s AND %s",
+ Manifest.permission.READ_WRITE_OWN_VOICEMAIL,
+ Manifest.permission.READ_WRITE_ALL_VOICEMAIL));
+ }
+ }
+
+ /** Determines if the given package has access to its own voicemails. */
+ public boolean packageHasOwnVoicemailAccess(String packageName) {
+ return packageHasPermission(packageName, Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
+ }
+
+ /** Determines if the given package has full access. */
+ public boolean packageHasFullAccess(String packageName) {
+ return packageHasPermission(packageName, Manifest.permission.READ_WRITE_OWN_VOICEMAIL) &&
+ packageHasPermission(packageName, Manifest.permission.READ_WRITE_ALL_VOICEMAIL);
+ }
+
+ /** Determines if the given package has the given permission. */
+ private boolean packageHasPermission(String packageName, String permission) {
+ return mContext.getPackageManager().checkPermission(permission, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /** Determines if the calling process has the given permission. */
+ private boolean callerHasPermission(String permission) {
+ return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
+ }
+}
diff --git a/src/com/android/providers/contacts/util/DbQueryUtils.java b/src/com/android/providers/contacts/util/DbQueryUtils.java
index 0045c59..58c8bb1 100644
--- a/src/com/android/providers/contacts/util/DbQueryUtils.java
+++ b/src/com/android/providers/contacts/util/DbQueryUtils.java
@@ -31,10 +31,19 @@ public class DbQueryUtils {
/** Returns a WHERE clause asserting equality of a field to a value. */
public static String getEqualityClause(String field, String value) {
+ return getClauseWithOperator(field, "=", value);
+ }
+
+ /** Returns a WHERE clause asserting in-equality of a field to a value. */
+ public static String getInequalityClause(String field, String value) {
+ return getClauseWithOperator(field, "!=", value);
+ }
+
+ private static String getClauseWithOperator(String field, String operator, String value) {
StringBuilder clause = new StringBuilder();
clause.append("(");
clause.append(field);
- clause.append(" = ");
+ clause.append(" ").append(operator).append(" ");
DatabaseUtils.appendEscapedSQLString(clause, value);
clause.append(")");
return clause.toString();
diff --git a/src/com/android/providers/contacts/util/SelectionBuilder.java b/src/com/android/providers/contacts/util/SelectionBuilder.java
new file mode 100644
index 0000000..14499e8
--- /dev/null
+++ b/src/com/android/providers/contacts/util/SelectionBuilder.java
@@ -0,0 +1,63 @@
+/*
+ * 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.util;
+
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builds a selection clause by concatenating several clauses with AND.
+ */
+public class SelectionBuilder {
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private final List<String> mWhereClauses;
+
+ /**
+ * @param baseSelection The base selection to start with. This is typically
+ * the user supplied selection arg. Pass null if no base selection is
+ * required.
+ */
+ public SelectionBuilder(String baseSelection) {
+ mWhereClauses = new ArrayList<String>();
+ addClause(baseSelection);
+ }
+
+ /**
+ * Adds a new clause to the selection. Nothing is added if the supplied clause
+ * is null or empty.
+ */
+ public SelectionBuilder addClause(String clause) {
+ if (!TextUtils.isEmpty(clause)) {
+ mWhereClauses.add(clause);
+ }
+ return this;
+ }
+
+ /**
+ * Returns a combined selection clause with AND of all clauses added using
+ * {@link #addClause(String)}. Returns null if no clause has been added or
+ * only null/empty clauses have been added till now.
+ */
+ public String build() {
+ if (mWhereClauses.size() == 0) {
+ return null;
+ }
+ return DbQueryUtils.concatenateClauses(mWhereClauses.toArray(EMPTY_STRING_ARRAY));
+ }
+}
diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
index d4da602..5a55311 100644
--- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java
+++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java
@@ -48,6 +48,13 @@ import android.test.suitebuilder.annotation.MediumTest;
*/
@MediumTest
public class CallLogProviderTest extends BaseContactsProvider2Test {
+ private static final String VOICEMAIL_ALL_PERMISSION =
+ "com.android.voicemail.permission.READ_WRITE_ALL_VOICEMAIL";
+ private static final String VOICEMAIL_OWN_PERMISSION =
+ "com.android.voicemail.permission.READ_WRITE_OWN_VOICEMAIL";
+
+ private static final Uri VOICEMAIL_CONTENT_URI = CallLogProvider.CONTENT_URI_WITH_VOICEMAIL;
+
/** Fields specific to voicemail provider that should not be exposed by call_log*/
private static final String[] VOICEMAIL_PROVIDER_SPECIFIC_COLUMNS = new String[] {
Voicemails._DATA,
@@ -75,21 +82,47 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
addProvider(TestCallLogProvider.class, CallLog.AUTHORITY);
}
+ @Override
+ protected void tearDown() throws Exception {
+ setUpWithVoicemailPermissions();
+ mResolver.delete(VOICEMAIL_CONTENT_URI, null, null);
+ super.tearDown();
+ }
+
public void testInsert_RegularCallRecord() {
- ContentValues values = new ContentValues();
- putCallValues(values);
+ ContentValues values = getDefaultCallValues();
Uri uri = mResolver.insert(Calls.CONTENT_URI, values);
values.put(Calls.COUNTRY_ISO, "us");
assertStoredValues(uri, values);
assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri));
}
+ private void setUpWithVoicemailPermissions() {
+ mActor.addPermissions(VOICEMAIL_OWN_PERMISSION);
+ mActor.addPermissions(VOICEMAIL_ALL_PERMISSION);
+ }
+
+ private void setUpWithNoVoicemailPermissions() {
+ mActor.removePermissions(VOICEMAIL_OWN_PERMISSION);
+ mActor.removePermissions(VOICEMAIL_ALL_PERMISSION);
+ }
+
public void testInsert_VoicemailCallRecord() {
- ContentValues values = new ContentValues();
- putCallValues(values);
+ setUpWithVoicemailPermissions();
+ final ContentValues values = getDefaultCallValues();
values.put(Calls.TYPE, Calls.VOICEMAIL_TYPE);
values.put(Calls.VOICEMAIL_URI, "content://foo/voicemail/2");
- Uri uri = mResolver.insert(Calls.CONTENT_URI, values);
+
+ // Should fail with the base content uri without the voicemail param.
+ EvenMoreAsserts.assertThrows(IllegalArgumentException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.insert(Calls.CONTENT_URI, values);
+ }
+ });
+
+ // Now grant voicemail permission - should succeed.
+ Uri uri = mResolver.insert(VOICEMAIL_CONTENT_URI, values);
assertStoredValues(uri, values);
assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri));
}
@@ -126,8 +159,7 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
}
public void testCallLogFilter() {
- ContentValues values = new ContentValues();
- putCallValues(values);
+ ContentValues values = getDefaultCallValues();
mResolver.insert(Calls.CONTENT_URI, values);
Uri filterUri = Uri.withAppendedPath(Calls.CONTENT_FILTER_URI, "1-800-4664-411");
@@ -163,12 +195,25 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
assertStoredValues(uri, values);
}
+ // Test to check that the calls and voicemail uris returns expected results.
+ public void testDifferentContentUris() {
+ setUpWithVoicemailPermissions();
+ // Insert one voicemaail and two regular call record.
+ insertVoicemailRecord();
+ insertCallRecord();
+ insertCallRecord();
+
+ // With the default uri, only 2 call entries should be returned.
+ // With the voicemail uri all 3 should be returned.
+ assertEquals(2, getCount(Calls.CONTENT_URI, null, null));
+ assertEquals(3, getCount(VOICEMAIL_CONTENT_URI, null, null));
+ }
+
// 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);
+ final ContentValues values = getDefaultCallValues();
values.put(voicemailColumn, "foo");
EvenMoreAsserts.assertThrows("Column: " + voicemailColumn,
IllegalArgumentException.class, new Runnable() {
@@ -211,18 +256,78 @@ public class CallLogProviderTest extends BaseContactsProvider2Test {
}
}
- private void putCallValues(ContentValues values) {
- values.put(Calls.TYPE, Calls.INCOMING_TYPE);
+ public void testVoicemailPermissions_Insert() {
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.insert(VOICEMAIL_CONTENT_URI, getDefaultVoicemailValues());
+ }
+ });
+ // Should now succeed with permissions granted.
+ setUpWithVoicemailPermissions();
+ mResolver.insert(VOICEMAIL_CONTENT_URI, getDefaultVoicemailValues());
+ }
+
+ public void testVoicemailPermissions_Update() {
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.update(VOICEMAIL_CONTENT_URI, getDefaultVoicemailValues(), null, null);
+ }
+ });
+ // Should now succeed with permissions granted.
+ setUpWithVoicemailPermissions();
+ mResolver.update(VOICEMAIL_CONTENT_URI, getDefaultCallValues(), null, null);
+ }
+
+ public void testVoicemailPermissions_Query() {
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.query(VOICEMAIL_CONTENT_URI, null, null, null, null);
+ }
+ });
+ // Should now succeed with permissions granted.
+ setUpWithVoicemailPermissions();
+ mResolver.query(VOICEMAIL_CONTENT_URI, null, null, null, null);
+ }
+
+ public void testVoicemailPermissions_Delete() {
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.delete(VOICEMAIL_CONTENT_URI, null, null);
+ }
+ });
+ // Should now succeed with permissions granted.
+ setUpWithVoicemailPermissions();
+ mResolver.delete(VOICEMAIL_CONTENT_URI, null, null);
+ }
+
+ private ContentValues getDefaultValues(int callType) {
+ ContentValues values = new ContentValues();
+ values.put(Calls.TYPE, callType);
values.put(Calls.NUMBER, "1-800-4664-411");
values.put(Calls.DATE, 1000);
values.put(Calls.DURATION, 30);
values.put(Calls.NEW, 1);
+ return values;
+ }
+
+ private ContentValues getDefaultCallValues() {
+ return getDefaultValues(Calls.INCOMING_TYPE);
+ }
+
+ private ContentValues getDefaultVoicemailValues() {
+ return getDefaultValues(Calls.VOICEMAIL_TYPE);
}
private Uri insertCallRecord() {
- ContentValues values = new ContentValues();
- putCallValues(values);
- return mResolver.insert(Calls.CONTENT_URI, values);
+ return mResolver.insert(Calls.CONTENT_URI, getDefaultCallValues());
+ }
+
+ private Uri insertVoicemailRecord() {
+ return mResolver.insert(VOICEMAIL_CONTENT_URI, getDefaultVoicemailValues());
}
public static class TestCallLogProvider extends CallLogProvider {
diff --git a/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java b/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java
index 6cad686..350e971 100644
--- a/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java
+++ b/tests/src/com/android/providers/contacts/util/DBQueryUtilsTest.java
@@ -40,6 +40,10 @@ public class DBQueryUtilsTest extends AndroidTestCase {
assertEquals("(foo = 'bar')", DbQueryUtils.getEqualityClause("foo", "bar"));
}
+ public void testGetInEqualityClause() {
+ assertEquals("(foo != 'bar')", DbQueryUtils.getInequalityClause("foo", "bar"));
+ }
+
public void testConcatenateClauses() {
assertEquals("(first)", concatenateClauses("first"));
assertEquals("(first) AND (second)", concatenateClauses("first", "second"));
diff --git a/tests/src/com/android/providers/contacts/util/SelectionBuilderTest.java b/tests/src/com/android/providers/contacts/util/SelectionBuilderTest.java
new file mode 100644
index 0000000..7adbeca
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/util/SelectionBuilderTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.util;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Unit tests for {@link SelectionBuilder}.
+ */
+@SmallTest
+public class SelectionBuilderTest extends AndroidTestCase {
+ public void testEmptyClauses() {
+ assertEquals(null, new SelectionBuilder(null).build());
+ assertEquals(null, new SelectionBuilder("").build());
+ assertEquals(null, new SelectionBuilder("").addClause(null).build());
+ assertEquals(null, new SelectionBuilder(null).addClause("").build());
+ assertEquals(null, new SelectionBuilder(null).addClause("").addClause(null).build());
+ }
+
+ public void testNonEmptyClauses() {
+ assertEquals("(A)", new SelectionBuilder("A").build());
+ assertEquals("(A) AND (B=bar) AND (C='1')", new SelectionBuilder("A")
+ .addClause("B=bar")
+ .addClause("C='1'")
+ .build());
+
+ // Skips null and empty clauses.
+ assertEquals("(A) AND (B) AND (C)", new SelectionBuilder("A")
+ .addClause("")
+ .addClause("B")
+ .addClause(null)
+ .addClause("C")
+ .addClause(null)
+ .build());
+
+ // Use base selection with constructor.
+ assertEquals("(A)", new SelectionBuilder(null).addClause("A").build());
+ assertEquals("(A)", new SelectionBuilder("").addClause("A").build());
+ assertEquals("(A) AND (B)", new SelectionBuilder("A").addClause("B").build());
+ }
+}