diff options
author | Debashish Chatterjee <debashishc@google.com> | 2011-07-06 11:41:59 +0100 |
---|---|---|
committer | Debashish Chatterjee <debashishc@google.com> | 2011-07-08 17:09:50 +0100 |
commit | 9cf06e7bcb0be759f1c930412fd2e41eba4f5f03 (patch) | |
tree | 254b7aee9c79066cf56740ee582bab463ac2ae24 | |
parent | 83a60d38646265694ab9cfa88b9601c201edb303 (diff) | |
download | packages_providers_ContactsProvider-9cf06e7bcb0be759f1c930412fd2e41eba4f5f03.tar.gz packages_providers_ContactsProvider-9cf06e7bcb0be759f1c930412fd2e41eba4f5f03.tar.bz2 packages_providers_ContactsProvider-9cf06e7bcb0be759f1c930412fd2e41eba4f5f03.zip |
VoicemailStatus content provider implementation.
- New Voicemail.Delegate implementation for voicemail_status table.
- modified openFile() interface to simplify the interaction.
- UridData now has a getWhereClause() method that can be used by both
the tables to set selection clause based on the uriData.
- Imrpoved permission checks for ContentValues for
update/insert/bulkinsert operations.
Bug:4968719
Change-Id: I6a6173c58d9929ef952c7d7e95afb8bc5ff4157b
6 files changed, 429 insertions, 87 deletions
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java index 3a093c03..6cdde4ca 100644 --- a/src/com/android/providers/contacts/VoicemailContentProvider.java +++ b/src/com/android/providers/contacts/VoicemailContentProvider.java @@ -15,6 +15,8 @@ */ package com.android.providers.contacts; +import static android.provider.VoicemailContract.SOURCE_PACKAGE_FIELD; +import static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses; import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause; import com.android.providers.contacts.ContactsDatabaseHelper.Tables; @@ -33,6 +35,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Binder; import android.os.ParcelFileDescriptor; +import android.provider.BaseColumns; import android.provider.VoicemailContract; import android.provider.VoicemailContract.Voicemails; @@ -41,23 +44,28 @@ import java.util.ArrayList; import java.util.List; /** - * An implementation of the Voicemail content provider. + * An implementation of the Voicemail content provider. This class in the entry point for both + * voicemail content ('calls') table and 'voicemail_status' table. This class performs all common + * permission checks and then delegates database level operations to respective table delegate + * objects. */ public class VoicemailContentProvider extends ContentProvider implements VoicemailTable.DelegateHelper { private static final String TAG = "VoicemailContentProvider"; - private static final String VOICEMAILS_TABLE_NAME = Tables.CALLS; private ContentResolver mContentResolver; private VoicemailPermissions mVoicemailPermissions; private VoicemailTable.Delegate mVoicemailContentTable; + private VoicemailTable.Delegate mVoicemailStatusTable; @Override public boolean onCreate() { Context context = context(); mContentResolver = context.getContentResolver(); mVoicemailPermissions = new VoicemailPermissions(context); - mVoicemailContentTable = new VoicemailContentTable(VOICEMAILS_TABLE_NAME, context, + mVoicemailContentTable = new VoicemailContentTable(Tables.CALLS, context, + getDatabaseHelper(context), this); + mVoicemailStatusTable = new VoicemailStatusTable(Tables.VOICEMAIL_STATUS, context, getDatabaseHelper(context), this); return true; } @@ -79,19 +87,19 @@ public class VoicemailContentProvider extends ContentProvider // Special case: for illegal URIs, we return null rather than thrown an exception. return null; } - return mVoicemailContentTable.getType(uriData); + return getTableDelegate(uriData).getType(uriData); } @Override public int bulkInsert(Uri uri, ContentValues[] valuesArray) { - UriData uriData = checkPermissionsAndCreateUriData(uri); - return mVoicemailContentTable.bulkInsert(uriData, valuesArray); + UriData uriData = checkPermissionsAndCreateUriData(uri, valuesArray); + return getTableDelegate(uriData).bulkInsert(uriData, valuesArray); } @Override public Uri insert(Uri uri, ContentValues values) { - UriData uriData = checkPermissionsAndCreateUriData(uri); - return mVoicemailContentTable.insert(uriData, values); + UriData uriData = checkPermissionsAndCreateUriData(uri, values); + return getTableDelegate(uriData).insert(uriData, values); } @Override @@ -100,16 +108,16 @@ public class VoicemailContentProvider extends ContentProvider UriData uriData = checkPermissionsAndCreateUriData(uri); SelectionBuilder selectionBuilder = new SelectionBuilder(selection); selectionBuilder.addClause(getPackageRestrictionClause()); - return mVoicemailContentTable.query(uriData, projection, selectionBuilder.build(), + return getTableDelegate(uriData).query(uriData, projection, selectionBuilder.build(), selectionArgs, sortOrder); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - UriData uriData = checkPermissionsAndCreateUriData(uri); + UriData uriData = checkPermissionsAndCreateUriData(uri, values); SelectionBuilder selectionBuilder = new SelectionBuilder(selection); selectionBuilder.addClause(getPackageRestrictionClause()); - return mVoicemailContentTable.update(uriData, values, selectionBuilder.build(), + return getTableDelegate(uriData).update(uriData, values, selectionBuilder.build(), selectionArgs); } @@ -118,14 +126,30 @@ public class VoicemailContentProvider extends ContentProvider UriData uriData = checkPermissionsAndCreateUriData(uri); SelectionBuilder selectionBuilder = new SelectionBuilder(selection); selectionBuilder.addClause(getPackageRestrictionClause()); - return mVoicemailContentTable.delete(uriData, selectionBuilder.build(), selectionArgs); + return getTableDelegate(uriData).delete(uriData, selectionBuilder.build(), selectionArgs); } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { UriData uriData = checkPermissionsAndCreateUriData(uri); // openFileHelper() relies on "_data" column to be populated with the file path. - return mVoicemailContentTable.openFile(uriData, mode, openFileHelper(uri, mode)); + return getTableDelegate(uriData).openFile(uriData, mode); + } + + /** Returns the correct table delegate object that can handle this URI. */ + private VoicemailTable.Delegate getTableDelegate(UriData uriData) { + switch (uriData.getUriType()) { + case STATUS: + case STATUS_ID: + return mVoicemailStatusTable; + case VOICEMAILS: + case VOICEMAILS_ID: + return mVoicemailContentTable; + case NO_MATCH: + throw new IllegalStateException("Invalid uri type for uri: " + uriData.getUri()); + default: + throw new IllegalStateException("Impossible, all cases are covered."); + } } /** @@ -135,8 +159,10 @@ public class VoicemailContentProvider extends ContentProvider private final Uri mUri; private final String mId; private final String mSourcePackage; + private final VoicemailUriType mUriType; - public UriData(Uri uri, String id, String sourcePackage) { + public UriData(Uri uri, VoicemailUriType uriType, String id, String sourcePackage) { + mUriType = uriType; mUri = uri; mId = id; mSourcePackage = sourcePackage; @@ -167,22 +193,43 @@ public class VoicemailContentProvider extends ContentProvider return mSourcePackage; } + /** Gets the Voicemail URI type. */ + public final VoicemailUriType getUriType() { + return mUriType; + } + + /** Builds a where clause from the URI data. */ + public final String getWhereClause() { + return concatenateClauses( + (hasId() ? getEqualityClause(BaseColumns._ID, getId()) : null), + (hasSourcePackage() ? getEqualityClause(SOURCE_PACKAGE_FIELD, + getSourcePackage()) : null)); + } + /** Create a {@link UriData} corresponding to a given uri. */ public static UriData createUriData(Uri uri) { String sourcePackage = uri.getQueryParameter( VoicemailContract.PARAM_KEY_SOURCE_PACKAGE); List<String> segments = uri.getPathSegments(); - switch (createUriMatcher().match(uri)) { + VoicemailUriType uriType = createUriMatcher().match(uri); + switch (uriType) { case VOICEMAILS: - return new UriData(uri, null, sourcePackage); + case STATUS: + return new UriData(uri, uriType, null, sourcePackage); case VOICEMAILS_ID: - return new UriData(uri, segments.get(1), sourcePackage); + case STATUS_ID: + return new UriData(uri, uriType, segments.get(1), sourcePackage); case NO_MATCH: throw new IllegalArgumentException("Invalid URI: " + uri); default: throw new IllegalStateException("Impossible, all cases are covered"); } } + + private static TypedUriMatcherImpl<VoicemailUriType> createUriMatcher() { + return new TypedUriMatcherImpl<VoicemailUriType>( + VoicemailContract.AUTHORITY, VoicemailUriType.values()); + } } @Override @@ -214,21 +261,10 @@ public class VoicemailContentProvider extends ContentProvider // VoicemailTable.DelegateHelper interface. public void checkAndAddSourcePackageIntoValues(UriData uriData, ContentValues values) { // If content values don't contain the provider, calculate the right provider to use. - if (!values.containsKey(VoicemailContract.SOURCE_PACKAGE_FIELD)) { + if (!values.containsKey(SOURCE_PACKAGE_FIELD)) { String provider = uriData.hasSourcePackage() ? uriData.getSourcePackage() : getCallingPackage(); - values.put(VoicemailContract.SOURCE_PACKAGE_FIELD, provider); - } - // If you put a provider in the URI and in the values, they must match. - if (uriData.hasSourcePackage() && - values.containsKey(VoicemailContract.SOURCE_PACKAGE_FIELD)) { - if (!uriData.getSourcePackage().equals( - values.get(VoicemailContract.SOURCE_PACKAGE_FIELD))) { - throw new SecurityException( - "Provider in URI was " + uriData.getSourcePackage() + - " but doesn't match provider in ContentValues which was " - + values.get(VoicemailContract.SOURCE_PACKAGE_FIELD)); - } + values.put(SOURCE_PACKAGE_FIELD, provider); } // You must have access to the provider given in values. if (!mVoicemailPermissions.callerHasFullAccess()) { @@ -238,9 +274,26 @@ public class VoicemailContentProvider extends ContentProvider } } - private static TypedUriMatcherImpl<VoicemailUriType> createUriMatcher() { - return new TypedUriMatcherImpl<VoicemailUriType>( - VoicemailContract.AUTHORITY, VoicemailUriType.values()); + /** + * Checks that the source_package field is same in uriData and ContentValues, if it happens + * to be set in both. + */ + private void checkSourcePackageSameIfSet(UriData uriData, ContentValues values) { + if (uriData.hasSourcePackage() && values.containsKey(SOURCE_PACKAGE_FIELD)) { + if (!uriData.getSourcePackage().equals(values.get(SOURCE_PACKAGE_FIELD))) { + throw new SecurityException( + "source_package in URI was " + uriData.getSourcePackage() + + " but doesn't match source_package in ContentValues which was " + + values.get(SOURCE_PACKAGE_FIELD)); + } + } + } + + @Override + /** Implementation of {@link VoicemailTable.DelegateHelper#openDataFile(UriData, String)} */ + public ParcelFileDescriptor openDataFile(UriData uriData, String mode) + throws FileNotFoundException { + return openFileHelper(uriData.getUri(), mode); } /** @@ -255,15 +308,27 @@ public class VoicemailContentProvider extends ContentProvider } /** - * Checks that the callingProvider is same as voicemailProvider. Throws {@link + * Same as {@link #checkPackagePermission(UriData)}. In addition does permission check + * on the ContentValues. + */ + private UriData checkPermissionsAndCreateUriData(Uri uri, ContentValues... valuesArray) { + UriData uriData = checkPermissionsAndCreateUriData(uri); + for (ContentValues values : valuesArray) { + checkSourcePackageSameIfSet(uriData, values); + } + return uriData; + } + + /** + * Checks that the callingPackage is same as voicemailSourcePackage. Throws {@link * SecurityException} if they don't match. */ - private final void checkPackagesMatch(String callingProvider, String voicemailProvider, + private final void checkPackagesMatch(String callingPackage, String voicemailSourcePackage, Uri uri) { - if (!voicemailProvider.equals(callingProvider)) { + if (!voicemailSourcePackage.equals(callingPackage)) { String errorMsg = String.format("Permission denied for URI: %s\n. " + - "Provider %s cannot perform this operation for %s. Requires %s permission.", - uri, callingProvider, voicemailProvider, + "Package %s cannot perform this operation for %s. Requires %s permission.", + uri, callingPackage, voicemailSourcePackage, Manifest.permission.READ_WRITE_ALL_VOICEMAIL); throw new SecurityException(errorMsg); } @@ -272,7 +337,7 @@ public class VoicemailContentProvider extends ContentProvider /** * Checks that either the caller has READ_WRITE_ALL_VOICEMAIL permission, or has the * READ_WRITE_OWN_VOICEMAIL permission and is using a URI that matches - * /voicemail/source/[source-package] where [source-package] is the same as the calling + * /voicemail/?source_package=[source-package] where [source-package] is the same as the calling * package. * * @throws SecurityException if the check fails. @@ -280,7 +345,7 @@ public class VoicemailContentProvider extends ContentProvider private void checkPackagePermission(UriData uriData) { if (!mVoicemailPermissions.callerHasFullAccess()) { if (!uriData.hasSourcePackage()) { - // You cannot have a match if this is not a provider uri. + // You cannot have a match if this is not a provider URI. throw new SecurityException(String.format( "Provider %s does not have %s permission." + "\nPlease set query parameter '%s' in the URI.\nURI: %s", diff --git a/src/com/android/providers/contacts/VoicemailContentTable.java b/src/com/android/providers/contacts/VoicemailContentTable.java index 8f6f3bfb..0712a653 100644 --- a/src/com/android/providers/contacts/VoicemailContentTable.java +++ b/src/com/android/providers/contacts/VoicemailContentTable.java @@ -38,6 +38,7 @@ import android.provider.VoicemailContract.Voicemails; import android.util.Log; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; /** @@ -152,14 +153,13 @@ public class VoicemailContentTable implements VoicemailTable.Delegate { // Directly update the db because we cannot update voicemail_uri through external // update() due to projectionMap check. This also avoids unnecessary permission // checks that are already done as part of insert request. - db.update(mTableName, values, getWhereClause( - UriData.createUriData(newUri)), null); + db.update(mTableName, values, UriData.createUriData(newUri).getWhereClause(), null); } @Override public int delete(UriData uriData, String selection, String[] selectionArgs) { final SQLiteDatabase db = mDbHelper.getWritableDatabase(); - String combinedClause = concatenateClauses(selection, getWhereClause(uriData), + String combinedClause = concatenateClauses(selection, uriData.getWhereClause(), getCallTypeClause()); // Delete all the files associated with this query. Once we've deleted the rows, there will @@ -196,7 +196,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate { qb.setProjectionMap(sVoicemailProjectionMap); qb.setStrict(true); - String combinedClause = concatenateClauses(selection, getWhereClause(uriData), + String combinedClause = concatenateClauses(selection, uriData.getWhereClause(), getCallTypeClause()); SQLiteDatabase db = mDbHelper.getReadableDatabase(); Cursor c = qb.query(db, projection, combinedClause, selectionArgs, null, null, sortOrder); @@ -214,7 +214,7 @@ public class VoicemailContentTable implements VoicemailTable.Delegate { final SQLiteDatabase db = mDbHelper.getWritableDatabase(); // TODO: This implementation does not allow bulk update because it only accepts // URI that include message Id. I think we do want to support bulk update. - String combinedClause = concatenateClauses(selection, getWhereClause(uriData), + String combinedClause = concatenateClauses(selection, uriData.getWhereClause(), getCallTypeClause()); int count = db.update(mTableName, values, combinedClause, selectionArgs); if (count > 0) { @@ -261,25 +261,16 @@ public class VoicemailContentTable implements VoicemailTable.Delegate { } @Override - public ParcelFileDescriptor openFile(UriData uriData, String mode, - ParcelFileDescriptor openFileHelper) { + public ParcelFileDescriptor openFile(UriData uriData, String mode) + throws FileNotFoundException { + ParcelFileDescriptor fileDescriptor = mDelegateHelper.openDataFile(uriData, mode); // If the open succeeded, then update the has_content bit in the table. if (mode.contains("w")) { ContentValues contentValues = new ContentValues(); contentValues.put(Voicemails.HAS_CONTENT, 1); update(uriData, contentValues, null, null); } - return openFileHelper; - } - - private String getWhereClause(UriData uriData) { - return concatenateClauses( - (uriData.hasId() ? - getEqualityClause(Voicemails._ID, uriData.getId()) - : null), - (uriData.hasSourcePackage() ? - getEqualityClause(Voicemails.SOURCE_PACKAGE, uriData.getSourcePackage()) - : null)); + return fileDescriptor; } /** Creates a clause to restrict the selection to only voicemail call type.*/ diff --git a/src/com/android/providers/contacts/VoicemailStatusTable.java b/src/com/android/providers/contacts/VoicemailStatusTable.java new file mode 100644 index 00000000..1f1fa8be --- /dev/null +++ b/src/com/android/providers/contacts/VoicemailStatusTable.java @@ -0,0 +1,135 @@ +/* + * 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 static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses; + +import com.android.providers.contacts.VoicemailContentProvider.UriData; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.VoicemailContract.Status; + +/** + * Implementation of {@link VoicemailTable.Delegate} for the voicemail status table. + */ +public class VoicemailStatusTable implements VoicemailTable.Delegate { + private static final ProjectionMap sStatusProjectionMap = new ProjectionMap.Builder() + .add(Status._ID) + .add(Status.CONFIGURATION_STATE) + .add(Status.DATA_CHANNEL_STATE) + .add(Status.NOTIFICATION_CHANNEL_STATE) + .add(Status.SETTINGS_URI) + .add(Status.SOURCE_PACKAGE) + .add(Status.VOICEMAIL_ACCESS_URI) + .build(); + + private final String mTableName; + private final Context mContext; + private final SQLiteOpenHelper mDbHelper; + private final VoicemailTable.DelegateHelper mDelegateHelper; + + public VoicemailStatusTable(String tableName, Context context, SQLiteOpenHelper dbHelper, + VoicemailTable.DelegateHelper delegateHelper) { + mTableName = tableName; + mContext = context; + mDbHelper = dbHelper; + mDelegateHelper = delegateHelper; + } + + @Override + public Uri insert(UriData uriData, ContentValues values) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + ContentValues copiedValues = new ContentValues(values); + mDelegateHelper.checkAndAddSourcePackageIntoValues(uriData, copiedValues); + long rowId = db.insert(mTableName, null, copiedValues); + if (rowId > 0) { + Uri newUri = ContentUris.withAppendedId(uriData.getUri(), rowId); + mDelegateHelper.notifyChange(newUri, Intent.ACTION_PROVIDER_CHANGED); + return newUri; + } else { + return null; + } + } + + @Override + public int bulkInsert(UriData uriData, ContentValues[] valuesArray) { + throw new UnsupportedOperationException("bulkInsert is not supported for status table"); + } + + @Override + public int delete(UriData uriData, String selection, String[] selectionArgs) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + String combinedClause = concatenateClauses(selection, uriData.getWhereClause()); + int count = db.delete(mTableName, combinedClause, selectionArgs); + if (count > 0) { + mDelegateHelper.notifyChange(uriData.getUri(), Intent.ACTION_PROVIDER_CHANGED); + } + return count; + } + + @Override + public Cursor query(UriData uriData, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(mTableName); + qb.setProjectionMap(sStatusProjectionMap); + qb.setStrict(true); + + String combinedClause = concatenateClauses(selection, uriData.getWhereClause()); + SQLiteDatabase db = mDbHelper.getReadableDatabase(); + Cursor c = qb.query(db, projection, combinedClause, selectionArgs, null, null, sortOrder); + if (c != null) { + c.setNotificationUri(mContext.getContentResolver(), Status.CONTENT_URI); + } + return c; + } + + @Override + public int update(UriData uriData, ContentValues values, String selection, + String[] selectionArgs) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + String combinedClause = concatenateClauses(selection, uriData.getWhereClause()); + int count = db.update(mTableName, values, combinedClause, selectionArgs); + if (count > 0) { + mDelegateHelper.notifyChange(uriData.getUri(), Intent.ACTION_PROVIDER_CHANGED); + } + return count; + } + + @Override + public String getType(UriData uriData) { + if (uriData.hasId()) { + return Status.ITEM_TYPE; + } else { + return Status.DIR_TYPE; + } + } + + @Override + public ParcelFileDescriptor openFile(UriData uriData, String mode) { + throw new UnsupportedOperationException("File operation is not supported for status table"); + } +} diff --git a/src/com/android/providers/contacts/VoicemailTable.java b/src/com/android/providers/contacts/VoicemailTable.java index d0687758..cab48fd0 100644 --- a/src/com/android/providers/contacts/VoicemailTable.java +++ b/src/com/android/providers/contacts/VoicemailTable.java @@ -22,6 +22,8 @@ import android.database.Cursor; import android.net.Uri; import android.os.ParcelFileDescriptor; +import java.io.FileNotFoundException; + /** * Defines interfaces for communication between voicemail content provider and voicemail table * implementations. @@ -40,8 +42,8 @@ public interface VoicemailTable { public int update(UriData uriData, ContentValues values, String selection, String[] selectionArgs); public String getType(UriData uriData); - public ParcelFileDescriptor openFile(UriData uriData, String mode, - ParcelFileDescriptor openFileHelper); + public ParcelFileDescriptor openFile(UriData uriData, String mode) + throws FileNotFoundException; } /** @@ -63,5 +65,12 @@ public interface VoicemailTable { * Inserts source_package field into ContentValues. Used in insert operations. */ public void checkAndAddSourcePackageIntoValues(UriData uriData, ContentValues values); + + /** + * Opens the file pointed to by the column "_data". + * @throws FileNotFoundException + */ + public ParcelFileDescriptor openDataFile(UriData uriData, String mode) + throws FileNotFoundException; } -}
\ No newline at end of file +} diff --git a/src/com/android/providers/contacts/VoicemailUriType.java b/src/com/android/providers/contacts/VoicemailUriType.java index c3a33abe..eb1c3bda 100644 --- a/src/com/android/providers/contacts/VoicemailUriType.java +++ b/src/com/android/providers/contacts/VoicemailUriType.java @@ -23,7 +23,9 @@ import com.android.providers.contacts.util.UriType; enum VoicemailUriType implements UriType { NO_MATCH(null), VOICEMAILS("voicemail"), - VOICEMAILS_ID("voicemail/#"); + VOICEMAILS_ID("voicemail/#"), + STATUS("status"), + STATUS_ID("status/#"); private final String path; diff --git a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java index 98498bdd..d0c8d5c4 100644 --- a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java +++ b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java @@ -27,10 +27,12 @@ import android.database.Cursor; import android.net.Uri; import android.provider.CallLog.Calls; import android.provider.VoicemailContract; +import android.provider.VoicemailContract.Status; import android.provider.VoicemailContract.Voicemails; import android.test.MoreAsserts; import java.io.File; +import java.io.FileNotFoundException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; @@ -110,14 +112,22 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { mUseSourceUri = true; } - private Uri contentUri() { + /** Returns the appropriate /voicemail URI. */ + private Uri voicemailUri() { return mUseSourceUri ? Voicemails.buildSourceUri(mActor.packageName) : Voicemails.CONTENT_URI; } + /** Returns the appropriate /status URI. */ + private Uri statusUri() { + return mUseSourceUri ? + Status.buildSourceUri(mActor.packageName) : Status.CONTENT_URI; + } + + public void testInsert() throws Exception { - ContentValues values = getDefaultVoicemailValues(); - Uri uri = mResolver.insert(contentUri(), values); + ContentValues values = getTestVoicemailValues(); + Uri uri = mResolver.insert(voicemailUri(), values); assertStoredValues(uri, values); assertSelection(uri, values, Voicemails._ID, ContentUris.parseId(uri)); assertEquals(1, countFilesInTestDirectory()); @@ -157,7 +167,7 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { public void testDelete() { Uri uri = insertVoicemail(); - int count = mResolver.delete(contentUri(), Voicemails._ID + "=" + int count = mResolver.delete(voicemailUri(), Voicemails._ID + "=" + ContentUris.parseId(uri), null); assertEquals(1, count); assertEquals(0, getCount(uri, null, null)); @@ -165,12 +175,12 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { public void testGetType() throws Exception { // voicemail with no MIME type. - ContentValues values = getDefaultVoicemailValues(); - Uri uri = mResolver.insert(contentUri(), values); + ContentValues values = getTestVoicemailValues(); + Uri uri = mResolver.insert(voicemailUri(), values); assertEquals(null, mResolver.getType(uri)); values.put(Voicemails.MIME_TYPE, "foo/bar"); - uri = mResolver.insert(contentUri(), values); + uri = mResolver.insert(voicemailUri(), values); assertEquals("foo/bar", mResolver.getType(uri)); // base URIs. @@ -185,14 +195,14 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { @Override public void run() { - mResolver.insert(Voicemails.CONTENT_URI, getDefaultVoicemailValues()); + mResolver.insert(Voicemails.CONTENT_URI, getTestVoicemailValues()); } }); EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { @Override public void run() { - mResolver.update(Voicemails.CONTENT_URI, getDefaultVoicemailValues(), null, null); + mResolver.update(Voicemails.CONTENT_URI, getTestVoicemailValues(), null, null); } }); @@ -216,11 +226,11 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { // Insert two records - one each with own and another package. insertVoicemail(); insertVoicemailForSourcePackage("another-package"); - assertEquals(2, getCount(contentUri(), null, null)); + assertEquals(2, getCount(voicemailUri(), null, null)); // Now give away full permission and check that only 1 message is accessible. setUpForOwnPermission(); - assertEquals(1, getCount(contentUri(), null, null)); + assertEquals(1, getCount(voicemailUri(), null, null)); // Once again try to insert message for another package. This time // it should fail. @@ -237,13 +247,13 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { // 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)); + assertEquals(2, getCount(voicemailUri(), null, null)); // Now give away full permission and check that we can update and delete only // the own voicemail. setUpForOwnPermission(); mResolver.update(withSourcePackageParam(ownVoicemail), - getDefaultVoicemailValues(), null, null); + getTestVoicemailValues(), null, null); mResolver.delete(withSourcePackageParam(ownVoicemail), null, null); // However, attempting to update or delete another-package's voicemail should fail. @@ -273,25 +283,25 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { @Override public void run() { - mResolver.insert(contentUri(), getDefaultVoicemailValues()); + mResolver.insert(voicemailUri(), getTestVoicemailValues()); } }); EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { @Override public void run() { - mResolver.update(contentUri(), getDefaultVoicemailValues(), null, null); + mResolver.update(voicemailUri(), getTestVoicemailValues(), null, null); } }); EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { @Override public void run() { - mResolver.query(contentUri(), null, null, null, null); + mResolver.query(voicemailUri(), null, null, null, null); } }); EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { @Override public void run() { - mResolver.delete(contentUri(), null, null); + mResolver.delete(voicemailUri(), null, null); } }); } @@ -300,13 +310,13 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { // insertable through voicemail provider. public void testCannotAccessCallLogSpecificFields_Insert() { for (String callLogColumn : CALLLOG_PROVIDER_SPECIFIC_COLUMNS) { - final ContentValues values = getDefaultVoicemailValues(); + final ContentValues values = getTestVoicemailValues(); values.put(callLogColumn, "foo"); EvenMoreAsserts.assertThrows("Column: " + callLogColumn, IllegalArgumentException.class, new Runnable() { @Override public void run() { - mResolver.insert(contentUri(), values); + mResolver.insert(voicemailUri(), values); } }); } @@ -316,7 +326,7 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { // exposed through voicemail provider query. public void testCannotAccessCallLogSpecificFields_Query() { // Query. - Cursor cursor = mResolver.query(contentUri(), null, null, null, null); + Cursor cursor = mResolver.query(voicemailUri(), null, null, null, null); List<String> columnNames = Arrays.asList(cursor.getColumnNames()); assertEquals(NUM_VOICEMAIL_FIELDS, columnNames.size()); // None of the call_log provider specific columns should be present. @@ -331,7 +341,7 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { public void testCannotAccessCallLogSpecificFields_Update() { for (String callLogColumn : CALLLOG_PROVIDER_SPECIFIC_COLUMNS) { final Uri insertedUri = insertVoicemail(); - final ContentValues values = getDefaultVoicemailValues(); + final ContentValues values = getTestVoicemailValues(); values.put(callLogColumn, "foo"); EvenMoreAsserts.assertThrows("Column: " + callLogColumn, IllegalArgumentException.class, new Runnable() { @@ -343,22 +353,137 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { } } + // Tests for voicemail status table. + + public void testStatusInsert() throws Exception { + ContentValues values = getTestStatusValues(); + Uri uri = mResolver.insert(statusUri(), values); + assertStoredValues(uri, values); + assertSelection(uri, values, Status._ID, ContentUris.parseId(uri)); + } + + // Test to ensure that duplicate entries for the same package still end up as the same record. + public void testStatusInsertDuplicate() throws Exception { + setUpForFullPermission(); + ContentValues values = getTestStatusValues(); + assertNotNull(mResolver.insert(statusUri(), values)); + assertEquals(1, getCount(statusUri(), null, null)); + + // Insertion request for the same package should fail with no change in count. + values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_NO_CONNECTION); + assertNull(mResolver.insert(statusUri(), values)); + assertEquals(1, getCount(statusUri(), null, null)); + + // Now insert entry for another source package, and it should end up as a separate record. + values.put(Status.SOURCE_PACKAGE, "another.package"); + assertNotNull(mResolver.insert(statusUri(), values)); + assertEquals(2, getCount(statusUri(), null, null)); + } + + public void testStatusUpdate() throws Exception { + Uri uri = insertTestStatusEntry(); + ContentValues values = getTestStatusValues(); + values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_NO_CONNECTION); + values.put(Status.NOTIFICATION_CHANNEL_STATE, + Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING); + int count = mResolver.update(uri, values, null, null); + assertEquals(1, count); + assertStoredValues(uri, values); + } + + public void testStatusDelete() { + Uri uri = insertTestStatusEntry(); + int count = mResolver.delete(statusUri(), Status._ID + "=" + + ContentUris.parseId(uri), null); + assertEquals(1, count); + assertEquals(0, getCount(uri, null, null)); + } + + public void testStatusGetType() throws Exception { + // Item URI. + Uri uri = insertTestStatusEntry(); + assertEquals(Status.ITEM_TYPE, mResolver.getType(uri)); + + // base URIs. + assertEquals(Status.DIR_TYPE, mResolver.getType(Status.CONTENT_URI)); + assertEquals(Status.DIR_TYPE, mResolver.getType(Status.buildSourceUri("foo"))); + } + + // Basic permission checks for the status table. + public void testStatusPermissions() throws Exception { + final ContentValues values = getTestStatusValues(); + // Inserting for another package should fail with any of the URIs. + values.put(Status.SOURCE_PACKAGE, "another.package"); + EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { + @Override + public void run() { + mResolver.insert(Status.CONTENT_URI, values); + } + }); + EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { + @Override + public void run() { + mResolver.insert(Status.buildSourceUri(mActor.packageName), values); + } + }); + + // But insertion with own package should succeed with the right uri. + values.put(Status.SOURCE_PACKAGE, mActor.packageName); + final Uri uri = mResolver.insert(Status.buildSourceUri(mActor.packageName), values); + assertNotNull(uri); + + // Updating source_package should not work as well. + values.put(Status.SOURCE_PACKAGE, "another.package"); + EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() { + @Override + public void run() { + mResolver.update(uri, values, null, null); + } + }); + } + + // File operation is not supported by /status URI. + public void testStatusFileOperation() throws Exception { + final Uri uri = insertTestStatusEntry(); + EvenMoreAsserts.assertThrows(UnsupportedOperationException.class, new Runnable() { + @Override + public void run() { + try { + mResolver.openOutputStream(uri); + } catch (FileNotFoundException e) { + fail("Unexpected exception " + e); + } + } + }); + + EvenMoreAsserts.assertThrows(UnsupportedOperationException.class, new Runnable() { + @Override + public void run() { + try { + mResolver.openInputStream(uri); + } catch (FileNotFoundException e) { + fail("Unexpected exception " + e); + } + } + }); + } + /** * Inserts a voicemail record with no source package set. The content provider * will detect source package. */ private Uri insertVoicemail() { - return mResolver.insert(contentUri(), getDefaultVoicemailValues()); + return mResolver.insert(voicemailUri(), getTestVoicemailValues()); } /** Inserts a voicemail record for the specified source package. */ private Uri insertVoicemailForSourcePackage(String sourcePackage) { - ContentValues values = getDefaultVoicemailValues(); + ContentValues values = getTestVoicemailValues(); values.put(Voicemails.SOURCE_PACKAGE, sourcePackage); - return mResolver.insert(contentUri(), values); + return mResolver.insert(voicemailUri(), values); } - private ContentValues getDefaultVoicemailValues() { + private ContentValues getTestVoicemailValues() { ContentValues values = new ContentValues(); values.put(Voicemails.NUMBER, "1-800-4664-411"); values.put(Voicemails.DATE, 1000); @@ -370,6 +495,21 @@ public class VoicemailProviderTest extends BaseContactsProvider2Test { return values; } + private Uri insertTestStatusEntry() { + return mResolver.insert(statusUri(), getTestStatusValues()); + } + + private ContentValues getTestStatusValues() { + ContentValues values = new ContentValues(); + values.put(Status.SOURCE_PACKAGE, mActor.packageName); + values.put(Status.VOICEMAIL_ACCESS_URI, "tel:901"); + values.put(Status.SETTINGS_URI, "com.example.voicemail.source.SettingsActivity"); + values.put(Status.CONFIGURATION_STATE, Status.CONFIGURATION_STATE_OK); + values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_OK); + values.put(Status.NOTIFICATION_CHANNEL_STATE, Status.NOTIFICATION_CHANNEL_STATE_OK); + 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); |