diff options
author | Debashish Chatterjee <debashishc@google.com> | 2011-06-20 17:03:26 +0100 |
---|---|---|
committer | Debashish Chatterjee <debashishc@google.com> | 2011-06-28 15:04:46 +0100 |
commit | aafbe295d67686870c64c74a59e589d1dfb506fa (patch) | |
tree | f413f2484f7cb4e1fe534ee35ba456deb150aad6 | |
parent | 836bbd3dc178f94bb674733b8ad57c765fff311a (diff) | |
download | packages_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
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()); + } +} |