summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDebashish Chatterjee <debashishc@google.com>2011-07-06 11:41:59 +0100
committerDebashish Chatterjee <debashishc@google.com>2011-07-08 17:09:50 +0100
commit9cf06e7bcb0be759f1c930412fd2e41eba4f5f03 (patch)
tree254b7aee9c79066cf56740ee582bab463ac2ae24
parent83a60d38646265694ab9cfa88b9601c201edb303 (diff)
downloadpackages_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
-rw-r--r--src/com/android/providers/contacts/VoicemailContentProvider.java145
-rw-r--r--src/com/android/providers/contacts/VoicemailContentTable.java27
-rw-r--r--src/com/android/providers/contacts/VoicemailStatusTable.java135
-rw-r--r--src/com/android/providers/contacts/VoicemailTable.java15
-rw-r--r--src/com/android/providers/contacts/VoicemailUriType.java4
-rw-r--r--tests/src/com/android/providers/contacts/VoicemailProviderTest.java190
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);