diff options
author | Debashish Chatterjee <debashishc@google.com> | 2011-06-16 15:35:03 +0100 |
---|---|---|
committer | Debashish Chatterjee <debashishc@google.com> | 2011-06-16 17:51:20 +0100 |
commit | 1975b56a3368b4b7684429ffa79e7b9dbc35b475 (patch) | |
tree | 018f5d6d84bb2c3cf55ad5807c9e20aa159b4472 | |
parent | 07b66b78439296cd27e1ca7c156bc9eeeae85131 (diff) | |
download | packages_providers_ContactsProvider-1975b56a3368b4b7684429ffa79e7b9dbc35b475.zip packages_providers_ContactsProvider-1975b56a3368b4b7684429ffa79e7b9dbc35b475.tar.gz packages_providers_ContactsProvider-1975b56a3368b4b7684429ffa79e7b9dbc35b475.tar.bz2 |
Unit tests for voicemail provider.
These tests cover basic functionality of the provider including
permission checks and media content input/output.
The key functionality that is yet to be tested is provider
change broadcast intents. This requires us to use a mocking framework,
and we are yet to finalize on which one we will use.
Change-Id: I2304309c4fc109cc1e0b969ede33d8268a4d4194
-rw-r--r-- | src/com/android/providers/contacts/VoicemailContentProvider.java | 71 | ||||
-rw-r--r-- | tests/src/com/android/providers/contacts/VoicemailProviderTest.java | 336 |
2 files changed, 375 insertions, 32 deletions
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java index d559758..c24fc03 100644 --- a/src/com/android/providers/contacts/VoicemailContentProvider.java +++ b/src/com/android/providers/contacts/VoicemailContentProvider.java @@ -86,11 +86,15 @@ public class VoicemailContentProvider extends ContentProvider { Context context = context(); mContentResolver = context.getContentResolver(); - mDbHelper = ContactsDatabaseHelper.getInstance(context); + mDbHelper = getDatabaseHelper(context); return true; } + /*package for testing*/ ContactsDatabaseHelper getDatabaseHelper(Context context) { + return ContactsDatabaseHelper.getInstance(context); + } + /*package for testing*/ Context context() { return getContext(); } @@ -135,7 +139,7 @@ public class VoicemailContentProvider extends ContentProvider { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - checkHasOwnPermission(); + checkCallerHasOwnPermission(); UriData uriData = createUriData(uri); checkPackagePermission(uriData); return queryInternal(uriData, projection, @@ -178,7 +182,7 @@ public class VoicemailContentProvider extends ContentProvider { @Override public int bulkInsert(Uri uri, ContentValues[] valuesArray) { - checkHasOwnPermission(); + checkCallerHasOwnPermission(); // 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); @@ -196,28 +200,29 @@ public class VoicemailContentProvider extends ContentProvider { @Override public Uri insert(Uri uri, ContentValues values) { - checkHasOwnPermission(); + checkCallerHasOwnPermission(); return insertInternal(createUriData(uri), values, true); } private Uri insertInternal(UriData uriData, ContentValues values, boolean sendProviderChangedNotification) { + ContentValues copiedValues = new ContentValues(values); checkInsertSupported(uriData); - checkAndAddSourcePackageIntoValues(uriData, values); + checkAndAddSourcePackageIntoValues(uriData, copiedValues); // "_data" column is used by base ContentProvider's openFileHelper() to determine filename // when Input/Output stream is requested to be opened. - values.put(Voicemails._DATA, generateDataFile()); + copiedValues.put(Voicemails._DATA, generateDataFile()); // call type is always voicemail. - values.put(Calls.TYPE, Calls.VOICEMAIL_TYPE); + copiedValues.put(Calls.TYPE, Calls.VOICEMAIL_TYPE); SQLiteDatabase db = mDbHelper.getWritableDatabase(); - long rowId = db.insert(VOICEMAILS_TABLE_NAME, null, values); + long rowId = db.insert(VOICEMAILS_TABLE_NAME, null, copiedValues); if (rowId > 0) { Uri newUri = ContentUris.withAppendedId( Uri.withAppendedPath(VoicemailContract.CONTENT_URI_SOURCE, - values.getAsString(Voicemails.SOURCE_PACKAGE)), rowId); + copiedValues.getAsString(Voicemails.SOURCE_PACKAGE)), rowId); if (sendProviderChangedNotification) { notifyChange(newUri, VoicemailContract.ACTION_NEW_VOICEMAIL, @@ -248,14 +253,14 @@ public class VoicemailContentProvider extends ContentProvider { // If you put a provider in the URI and in the values, they must match. if (uriData.hasSourcePackage() && values.containsKey(Voicemails.SOURCE_PACKAGE)) { if (!uriData.getSourcePackage().equals(values.get(Voicemails.SOURCE_PACKAGE))) { - throw new IllegalArgumentException( + throw new SecurityException( "Provider in URI was " + uriData.getSourcePackage() + " but doesn't match provider in ContentValues which was " + values.get(Voicemails.SOURCE_PACKAGE)); } } // You must have access to the provider given in values. - if (!hasFullPermission(getCallingPackage())) { + if (!callerHasFullPermission()) { checkPackagesMatch(getCallingPackage(), values.getAsString(Voicemails.SOURCE_PACKAGE), uriData.getUri()); } @@ -286,7 +291,7 @@ public class VoicemailContentProvider extends ContentProvider { @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - checkHasOwnPermission(); + checkCallerHasOwnPermission(); UriData uriData = createUriData(uri); checkUpdateSupported(uriData); checkPackagePermission(uriData); @@ -311,7 +316,7 @@ public class VoicemailContentProvider extends ContentProvider { @Override public int delete(Uri uri, String selection, String[] selectionArgs) { - checkHasOwnPermission(); + checkCallerHasOwnPermission(); UriData uriData = createUriData(uri); checkPackagePermission(uriData); final SQLiteDatabase db = mDbHelper.getWritableDatabase(); @@ -347,7 +352,7 @@ public class VoicemailContentProvider extends ContentProvider { @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - checkHasOwnPermission(); + checkCallerHasOwnPermission(); UriData uriData = createUriData(uri); checkPackagePermission(uriData); @@ -451,7 +456,7 @@ public class VoicemailContentProvider extends ContentProvider { * @throws SecurityException if the check fails. */ private void checkPackagePermission(UriData uriData) { - if (!hasFullPermission(getCallingPackage())) { + if (!callerHasFullPermission()) { if (!uriData.hasSourcePackage()) { // You cannot have a match if this is not a provider uri. throw new SecurityException(String.format( @@ -513,11 +518,11 @@ public class VoicemailContentProvider extends ContentProvider { // which one we return. String bestSoFar = callerPackages[0]; for (String callerPackage : callerPackages) { - if (hasFullPermission(callerPackage)) { + if (hasPermission(callerPackage, Manifest.permission.READ_WRITE_ALL_VOICEMAIL)) { // Full always wins, we can return early. return callerPackage; } - if (hasOwnPermission(callerPackage)) { + if (hasPermission(callerPackage, Manifest.permission.READ_WRITE_OWN_VOICEMAIL)) { bestSoFar = callerPackage; } } @@ -529,29 +534,31 @@ public class VoicemailContentProvider extends ContentProvider { * * @throws SecurityException if the caller does not have the voicemail source permission. */ - private void checkHasOwnPermission() { - if (!hasOwnPermission(getCallingPackage())) { + private void checkCallerHasOwnPermission() { + if (!callerHasOwnPermission()) { throw new SecurityException("The caller must have permission: " + Manifest.permission.READ_WRITE_OWN_VOICEMAIL); } } - /** Tells us if the given package has the source permission. */ - private boolean hasOwnPermission(String packageName) { - return hasPermission(packageName, 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); } - /** - * Tells us if the given package has the full permission and the source - * permission. - */ - private boolean hasFullPermission(String packageName) { - return hasOwnPermission(packageName) && - hasPermission(packageName, Manifest.permission.READ_WRITE_ALL_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; } - /** Tells us if the given package has the given permission. */ - /* package for test */boolean hasPermission(String packageName, String permission) { + /** Determines if the given package has the given permission. */ + boolean hasPermission(String packageName, String permission) { return context().getPackageManager().checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED; } @@ -561,7 +568,7 @@ public class VoicemailContentProvider extends ContentProvider { * access to all data. */ private String getPackageRestrictionClause() { - if (hasFullPermission(getCallingPackage())) { + if (callerHasFullPermission()) { return null; } return getEqualityClause(Voicemails.SOURCE_PACKAGE, getCallingPackage()); diff --git a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java new file mode 100644 index 0000000..8e06bf5 --- /dev/null +++ b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java @@ -0,0 +1,336 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package com.android.providers.contacts; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.net.Uri; +import android.provider.VoicemailContract; +import android.provider.VoicemailContract.Voicemails; +import android.test.MoreAsserts; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Unit tests for {@link VoicemailContentProvider}. + * + * Run the test like this: + * <code> + * runtest -c com.android.providers.contacts.VoicemailProviderTest contactsprov + * </code> + */ +public class VoicemailProviderTest extends BaseContactsProvider2Test { + private static final String ALL_PERMISSION = + "com.android.voicemail.permission.READ_WRITE_ALL_VOICEMAIL"; + private static final String OWN_PERMISSION = + "com.android.voicemail.permission.READ_WRITE_OWN_VOICEMAIL"; + @Override + protected Class<? extends ContentProvider> getProviderClass() { + return TestVoicemailProvider.class; + } + + @Override + protected String getAuthority() { + return VoicemailContract.AUTHORITY; + } + + private boolean mUseSourceUri = false; + private File mTestDirectory; + + @Override + protected void setUp() throws Exception { + super.setUp(); + addProvider(TestVoicemailProvider.class, VoicemailContract.AUTHORITY); + setUpForOwnPermission(); + TestVoicemailProvider.setVvmProviderCallDelegate(createMockProviderCalls()); + } + + @Override + protected void tearDown() throws Exception { + removeTestDirectory(); + super.tearDown(); + } + + private void setUpForOwnPermission() { + // Give away full permission, in case it was granted previously. + mActor.removePermissions(ALL_PERMISSION); + mActor.addPermissions(OWN_PERMISSION); + mUseSourceUri = false; + } + + private void setUpForFullPermission() { + mActor.addPermissions(OWN_PERMISSION); + mActor.addPermissions(ALL_PERMISSION); + mUseSourceUri = true; + } + + private void setUpForNoPermission() { + mActor.removePermissions(OWN_PERMISSION); + mActor.removePermissions(ALL_PERMISSION); + mUseSourceUri = true; + } + + private Uri contentUri() { + if (mUseSourceUri) { + return VoicemailContract.CONTENT_URI; + } else { + return Uri.withAppendedPath(VoicemailContract.CONTENT_URI_SOURCE, + mActor.packageName); + } + } + + public void testInsert() throws Exception { + ContentValues values = getDefaultVoicemailValues(); + Uri uri = mResolver.insert(contentUri(), values); + assertStoredValues(uri, values); + assertSelection(uri, values, Voicemails._ID, ContentUris.parseId(uri)); + assertEquals(1, countFilesInTestDirectory()); + } + + // Test to ensure that media content can be written and read back. + public void testFileContent() throws Exception { + Uri uri = insertVoicemail(); + OutputStream out = mResolver.openOutputStream(uri); + byte[] outBuffer = {0x1, 0x2, 0x3, 0x4}; + out.write(outBuffer); + out.flush(); + out.close(); + InputStream in = mResolver.openInputStream(uri); + byte[] inBuffer = new byte[4]; + int numBytesRead = in.read(inBuffer); + assertEquals(numBytesRead, outBuffer.length); + MoreAsserts.assertEquals(outBuffer, inBuffer); + // No more data should be left. + assertEquals(-1, in.read(inBuffer)); + in.close(); + } + + public void testUpdate() { + Uri uri = insertVoicemail(); + ContentValues values = new ContentValues(); + values.put(Voicemails.NUMBER, "1-800-263-7643"); + values.put(Voicemails.DATE, 2000); + values.put(Voicemails.DURATION, 40); + values.put(Voicemails.STATE, 2); + values.put(Voicemails.HAS_CONTENT, 1); + values.put(Voicemails.SOURCE_DATA, "foo"); + int count = mResolver.update(uri, values, null, null); + assertEquals(1, count); + assertStoredValues(uri, values); + } + + public void testDelete() { + Uri uri = insertVoicemail(); + int count = mResolver.delete(contentUri(), Voicemails._ID + "=" + + ContentUris.parseId(uri), null); + assertEquals(1, count); + assertEquals(0, getCount(uri, null, null)); + } + + public void testPermissions_InsertAndQuery() { + setUpForFullPermission(); + // Insert two records - one each with own and another package. + insertVoicemail(); + insertVoicemailForSourcePackage("another-package"); + assertEquals(2, getCount(contentUri(), null, null)); + + // Now give away full permission and check that only 1 message is accessible. + setUpForOwnPermission(); + assertEquals(1, getCount(contentUri(), null, null)); + + // Once again try to insert message for another package. This time + // it should fail. + EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { + @Override + public void run() { + insertVoicemailForSourcePackage("another-package"); + } + }); + } + + public void testPermissions_UpdateAndDelete() { + setUpForFullPermission(); + // Insert two records - one each with own and another package. + final Uri ownVoicemail = insertVoicemail(); + final Uri anotherVoicemail = insertVoicemailForSourcePackage("another-package"); + assertEquals(2, getCount(contentUri(), null, null)); + + // Now give away full permission and check that we can update and delete only + // the own voicemail. + setUpForOwnPermission(); + mResolver.update(ownVoicemail, getDefaultVoicemailValues(), null, null); + mResolver.delete(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); + } + }); + } + + // Test to ensure that all operations fail when no voicemail permission is granted. + public void testNoPermissions() { + setUpForNoPermission(); + EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { + @Override + public void run() { + mResolver.insert(contentUri(), getDefaultVoicemailValues()); + } + }); + EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { + @Override + public void run() { + mResolver.update(contentUri(), getDefaultVoicemailValues(), null, null); + } + }); + EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { + @Override + public void run() { + mResolver.query(contentUri(), null, null, null, null); + } + }); + EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { + @Override + public void run() { + mResolver.delete(contentUri(), null, null); + } + }); + } + + /** + * Inserts a voicemail record with no source package set. The content provider + * will detect source package. + */ + private Uri insertVoicemail() { + return mResolver.insert(contentUri(), getDefaultVoicemailValues()); + } + + /** Inserts a voicemail record for the specified source package. */ + private Uri insertVoicemailForSourcePackage(String sourcePackage) { + ContentValues values = getDefaultVoicemailValues(); + values.put(Voicemails.SOURCE_PACKAGE, sourcePackage); + return mResolver.insert(contentUri(), values); + } + + private ContentValues getDefaultVoicemailValues() { + ContentValues values = new ContentValues(); + values.put(Voicemails.NUMBER, "1-800-4664-411"); + values.put(Voicemails.DATE, 1000); + values.put(Voicemails.DURATION, 30); + values.put(Voicemails.NEW, 1); + values.put(Voicemails.HAS_CONTENT, 0); + values.put(Voicemails.SOURCE_DATA, "1234"); + values.put(Voicemails.STATE, Voicemails.STATE_INBOX); + return values; + } + + /** The calls that we need to mock out for our VvmProvider, used by TestVoicemailProvider. */ + public interface VvmProviderCalls { + public void sendOrderedBroadcast(Intent intent, String receiverPermission); + public File getDir(String name, int mode); + } + + public static class TestVoicemailProvider extends VoicemailContentProvider { + private static VvmProviderCalls mDelgate; + + public static synchronized void setVvmProviderCallDelegate(VvmProviderCalls delegate) { + mDelgate = delegate; + } + + @Override + protected ContactsDatabaseHelper getDatabaseHelper(Context context) { + return new ContactsDatabaseHelper(context); + } + + @Override + protected Context context() { + return new ContextWrapper(getContext()) { + @Override + public File getDir(String name, int mode) { + return mDelgate.getDir(name, mode); + } + }; + } + + @Override + protected String getCallingPackage() { + return getContext().getPackageName(); + } + } + + /** Lazily construct the test directory when required. */ + private synchronized File getTestDirectory() { + if (mTestDirectory == null) { + File baseDirectory = getContext().getCacheDir(); + mTestDirectory = new File(baseDirectory, Long.toString(System.currentTimeMillis())); + assertFalse(mTestDirectory.exists()); + assertTrue(mTestDirectory.mkdirs()); + } + return mTestDirectory; + } + + private void removeTestDirectory() { + if (mTestDirectory != null) { + recursiveDeleteAll(mTestDirectory); + } + } + + private static void recursiveDeleteAll(File input) { + if (input.isDirectory()) { + for (File file : input.listFiles()) { + recursiveDeleteAll(file); + } + } + assertTrue("error deleting " + input.getAbsolutePath(), input.delete()); + } + + private List<File> findAllFiles(File input) { + if (input == null) { + return Collections.emptyList(); + } + if (!input.isDirectory()) { + return Collections.singletonList(input); + } + List<File> results = new ArrayList<File>(); + for (File file : input.listFiles()) { + results.addAll(findAllFiles(file)); + } + return results; + } + + private int countFilesInTestDirectory() { + return findAllFiles(mTestDirectory).size(); + } + + // TODO: Use a mocking framework to mock these calls. + private VvmProviderCalls createMockProviderCalls() { + return new VvmProviderCalls() { + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission) { + // TODO: Auto-generated method stub + } + + @Override + public File getDir(String name, int mode) { + return getTestDirectory(); + } + }; + } +} |