Merge "Set the new last-access time global search column."
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 2e2af94..69cf2b4 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -65,6 +65,7 @@
import android.provider.ContactsContract.Settings;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.SocialContract.Activities;
+import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Voicemails;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
@@ -97,7 +98,7 @@
* 600-699 Ice Cream Sandwich
* </pre>
*/
- static final int DATABASE_VERSION = 603;
+ static final int DATABASE_VERSION = 604;
private static final String DATABASE_NAME = "contacts2.db";
private static final String DATABASE_PRESENCE = "presence_db";
@@ -124,6 +125,7 @@
public static final String DIRECTORIES = "directories";
public static final String DEFAULT_DIRECTORY = "default_directory";
public static final String SEARCH_INDEX = "search_index";
+ public static final String VOICEMAIL_STATUS = "voicemail_status";
/**
* For {@link ContactsContract.DataUsageFeedback}. The table structure itself
@@ -792,7 +794,7 @@
@Override
public void onCreate(SQLiteDatabase db) {
- Log.i(TAG, "Bootstrapping database");
+ Log.i(TAG, "Bootstrapping database version: " + DATABASE_VERSION);
mSyncState.createDatabase(db);
@@ -1080,6 +1082,17 @@
Voicemails.STATE + " INTEGER" +
");");
+ // Voicemail source status table.
+ db.execSQL("CREATE TABLE " + Tables.VOICEMAIL_STATUS + " (" +
+ VoicemailContract.Status._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ VoicemailContract.Status.SOURCE_PACKAGE + " TEXT UNIQUE NOT NULL," +
+ VoicemailContract.Status.SETTINGS_URI + " TEXT," +
+ VoicemailContract.Status.VOICEMAIL_ACCESS_URI + " TEXT," +
+ VoicemailContract.Status.CONFIGURATION_STATE + " INTEGER," +
+ VoicemailContract.Status.DATA_CHANNEL_STATE + " INTEGER," +
+ VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE + " INTEGER" +
+ ");");
+
// Activities table
db.execSQL("CREATE TABLE " + Tables.ACTIVITIES + " (" +
Activities._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
@@ -1969,6 +1982,11 @@
oldVersion = 603;
}
+ if (oldVersion < 604) {
+ upgradeToVersion604(db);
+ oldVersion = 604;
+ }
+
if (upgradeViewsAndTriggers) {
createContactsViews(db);
createGroupsView(db);
@@ -3043,6 +3061,18 @@
db.execSQL("ALTER TABLE calls ADD state INTEGER;");
}
+ private void upgradeToVersion604(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE voicemail_status (" +
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ "source_package TEXT UNIQUE NOT NULL," +
+ "settings_uri TEXT," +
+ "voicemail_access_uri TEXT," +
+ "configuration_state INTEGER," +
+ "data_channel_state INTEGER," +
+ "notification_channel_state INTEGER" +
+ ");");
+ }
+
public String extractHandleFromEmailAddress(String email) {
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email);
if (tokens.length == 0) {
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index 5e17a33..3a093c0 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -15,82 +15,50 @@
*/
package com.android.providers.contacts;
-import static com.android.providers.contacts.util.DbQueryUtils.checkForSupportedColumns;
-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;
+import com.android.providers.contacts.util.SelectionBuilder;
+import com.android.providers.contacts.util.TypedUriMatcherImpl;
+
+import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
+import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
-import android.provider.CallLog.Calls;
import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Voicemails;
-import android.util.Log;
-import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.ContactsDatabaseHelper.Views;
-import com.android.providers.contacts.util.CloseUtils;
-import com.android.providers.contacts.util.DbQueryUtils;
-import com.android.providers.contacts.util.TypedUriMatcherImpl;
-
-import java.io.File;
import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.HashMap;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
-// TODO: Restrict access to only voicemail columns (i.e no access to call_log
-// specific fields)
-// TODO: Port unit tests from perforce.
/**
* An implementation of the Voicemail content provider.
*/
-public class VoicemailContentProvider extends ContentProvider {
+public class VoicemailContentProvider extends ContentProvider
+ implements VoicemailTable.DelegateHelper {
private static final String TAG = "VoicemailContentProvider";
-
- /** The private directory in which to store the data associated with the voicemail. */
- private static final String DATA_DIRECTORY = "voicemail-data";
-
- private static final String[] MIME_TYPE_ONLY_PROJECTION = new String[] { Voicemails.MIME_TYPE };
- private static final String[] FILENAME_ONLY_PROJECTION = new String[] { Voicemails._DATA };
private static final String VOICEMAILS_TABLE_NAME = Tables.CALLS;
- // Voicemail projection map
- private static final ProjectionMap sVoicemailProjectionMap = new ProjectionMap.Builder()
- .add(Voicemails._ID)
- .add(Voicemails.NUMBER)
- .add(Voicemails.DATE)
- .add(Voicemails.DURATION)
- .add(Voicemails.NEW)
- .add(Voicemails.STATE)
- .add(Voicemails.SOURCE_DATA)
- .add(Voicemails.SOURCE_PACKAGE)
- .add(Voicemails.HAS_CONTENT)
- .add(Voicemails.MIME_TYPE)
- .add(Voicemails._DATA)
- .build();
private ContentResolver mContentResolver;
- private ContactsDatabaseHelper mDbHelper;
private VoicemailPermissions mVoicemailPermissions;
+ private VoicemailTable.Delegate mVoicemailContentTable;
@Override
public boolean onCreate() {
Context context = context();
mContentResolver = context.getContentResolver();
- mDbHelper = getDatabaseHelper(context);
mVoicemailPermissions = new VoicemailPermissions(context);
+ mVoicemailContentTable = new VoicemailContentTable(VOICEMAILS_TABLE_NAME, context,
+ getDatabaseHelper(context), this);
return true;
}
@@ -106,327 +74,64 @@
public String getType(Uri uri) {
UriData uriData = null;
try {
- uriData = createUriData(uri);
+ uriData = UriData.createUriData(uri);
} catch (IllegalArgumentException ignored) {
// Special case: for illegal URIs, we return null rather than thrown an exception.
return null;
}
- // TODO: DB lookup for the mime type may cause strict mode exception for the callers of
- // getType(). See if this could be avoided.
- if (uriData.hasId()) {
- // An individual voicemail - so lookup the MIME type in the db.
- return lookupMimeType(uriData);
- }
- // Not an individual voicemail - must be a directory listing type.
- return VoicemailContract.DIR_TYPE;
+ return mVoicemailContentTable.getType(uriData);
}
- /** Query the db for the MIME type of the given URI, called only from {@link #getType(Uri)}. */
- private String lookupMimeType(UriData uriData) {
- Cursor cursor = null;
- try {
- // Use queryInternal, bypassing provider permission check. This is needed because
- // getType() can be called from any application context (even without voicemail
- // permissions) to know the MIME type of the URI. There is no security issue here as we
- // do not expose any sensitive data through this interface.
- cursor = queryInternal(uriData, MIME_TYPE_ONLY_PROJECTION, null, null, null);
- if (cursor.moveToFirst()) {
- return cursor.getString(cursor.getColumnIndex(Voicemails.MIME_TYPE));
- }
- } finally {
- CloseUtils.closeQuietly(cursor);
- }
- return null;
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] valuesArray) {
+ UriData uriData = checkPermissionsAndCreateUriData(uri);
+ return mVoicemailContentTable.bulkInsert(uriData, valuesArray);
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ UriData uriData = checkPermissionsAndCreateUriData(uri);
+ return mVoicemailContentTable.insert(uriData, values);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
- mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
- UriData uriData = createUriData(uri);
- checkPackagePermission(uriData);
- return queryInternal(uriData, projection,
- concatenateClauses(selection, getPackageRestrictionClause()), selectionArgs,
- sortOrder);
- }
-
- /**
- * Internal version of query(), that does not apply any provider restriction and lets the query
- * flow through without such checks.
- * <p>
- * This is useful for internal queries when we do not worry about access permissions.
- */
- private Cursor queryInternal(UriData uriData, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables(Tables.CALLS);
- qb.setProjectionMap(sVoicemailProjectionMap);
- qb.setStrict(true);
-
- String combinedClause = concatenateClauses(selection, getWhereClause(uriData),
- getCallTypeClause());
- SQLiteDatabase db = mDbHelper.getReadableDatabase();
- Cursor c = qb.query(db, projection, combinedClause, selectionArgs, null, null, sortOrder);
- if (c != null) {
- c.setNotificationUri(mContentResolver, VoicemailContract.CONTENT_URI);
- }
- return c;
- }
-
- private String getWhereClause(UriData uriData) {
- return concatenateClauses(
- (uriData.hasId() ?
- getEqualityClause(Voicemails._ID, uriData.getId())
- : null),
- (uriData.hasSourcePackage() ?
- getEqualityClause(Voicemails.SOURCE_PACKAGE, uriData.getSourcePackage())
- : null));
- }
-
- @Override
- public int bulkInsert(Uri uri, ContentValues[] valuesArray) {
- mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
- // TODO: There is scope to optimize this method further. At the least we can avoid doing the
- // extra work related to the calling provider and checking permissions.
- UriData uriData = createUriData(uri);
- int numInserted = 0;
- for (ContentValues values : valuesArray) {
- if (insertInternal(uriData, values, false) != null) {
- numInserted++;
- }
- }
- if (numInserted > 0) {
- notifyChange(uri, Intent.ACTION_PROVIDER_CHANGED);
- }
- return numInserted;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
- return insertInternal(createUriData(uri), values, true);
- }
-
- private Uri insertInternal(UriData uriData, ContentValues values,
- boolean sendProviderChangedNotification) {
- checkForSupportedColumns(sVoicemailProjectionMap, values);
- ContentValues copiedValues = new ContentValues(values);
- checkInsertSupported(uriData);
- checkAndAddSourcePackageIntoValues(uriData, copiedValues);
-
- // "_data" column is used by base ContentProvider's openFileHelper() to determine filename
- // when Input/Output stream is requested to be opened.
- copiedValues.put(Voicemails._DATA, generateDataFile());
-
- // call type is always voicemail.
- copiedValues.put(Calls.TYPE, Calls.VOICEMAIL_TYPE);
-
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
- long rowId = db.insert(VOICEMAILS_TABLE_NAME, null, copiedValues);
- if (rowId > 0) {
- Uri newUri = ContentUris.withAppendedId(
- Uri.withAppendedPath(VoicemailContract.CONTENT_URI_SOURCE,
- copiedValues.getAsString(Voicemails.SOURCE_PACKAGE)), rowId);
-
- if (sendProviderChangedNotification) {
- notifyChange(newUri, VoicemailContract.ACTION_NEW_VOICEMAIL,
- Intent.ACTION_PROVIDER_CHANGED);
- } else {
- notifyChange(newUri, VoicemailContract.ACTION_NEW_VOICEMAIL);
- }
- // Populate the 'voicemail_uri' field to be used by the call_log provider.
- updateVoicemailUri(db, newUri);
- return newUri;
- }
- return null;
- }
-
- private void updateVoicemailUri(SQLiteDatabase db, Uri newUri) {
- ContentValues values = new ContentValues();
- values.put(Calls.VOICEMAIL_URI, newUri.toString());
- // 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(VOICEMAILS_TABLE_NAME, values, getWhereClause(createUriData(newUri)), null);
- }
-
- private void checkAndAddSourcePackageIntoValues(UriData uriData, ContentValues values) {
- // If content values don't contain the provider, calculate the right provider to use.
- if (!values.containsKey(Voicemails.SOURCE_PACKAGE)) {
- String provider = uriData.hasSourcePackage() ?
- uriData.getSourcePackage() : getCallingPackage();
- values.put(Voicemails.SOURCE_PACKAGE, provider);
- }
- // 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 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 (!mVoicemailPermissions.callerHasFullAccess()) {
- checkPackagesMatch(getCallingPackage(), values.getAsString(Voicemails.SOURCE_PACKAGE),
- uriData.getUri());
- }
- }
-
- /**
- * Checks that the callingProvider is same as voicemailProvider. Throws {@link
- * SecurityException} if they don't match.
- */
- private final void checkPackagesMatch(String callingProvider, String voicemailProvider,
- Uri uri) {
- if (!voicemailProvider.equals(callingProvider)) {
- String errorMsg = String.format("Permission denied for URI: %s\n. " +
- "Provider %s cannot perform this operation for %s. Requires %s permission.",
- uri, callingProvider, voicemailProvider,
- Manifest.permission.READ_WRITE_ALL_VOICEMAIL);
- throw new SecurityException(errorMsg);
- }
- }
-
- private void checkInsertSupported(UriData uriData) {
- if (uriData.hasId()) {
- throw new UnsupportedOperationException(String.format(
- "Cannot insert URI: %s. Inserted URIs should not contain an id.",
- uriData.getUri()));
- }
+ UriData uriData = checkPermissionsAndCreateUriData(uri);
+ SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+ selectionBuilder.addClause(getPackageRestrictionClause());
+ return mVoicemailContentTable.query(uriData, projection, selectionBuilder.build(),
+ selectionArgs, sortOrder);
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
- UriData uriData = createUriData(uri);
- checkPackagePermission(uriData);
- checkForSupportedColumns(sVoicemailProjectionMap, values);
- checkUpdateSupported(uriData);
- 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, getPackageRestrictionClause(),
- getWhereClause(uriData), getCallTypeClause());
- int count = db.update(VOICEMAILS_TABLE_NAME, values, combinedClause, selectionArgs);
- if (count > 0) {
- notifyChange(uri, Intent.ACTION_PROVIDER_CHANGED);
- }
- return count;
- }
-
- private void checkUpdateSupported(UriData uriData) {
- if (!uriData.hasId()) {
- throw new UnsupportedOperationException(String.format(
- "Cannot update URI: %s. Bulk update not supported", uriData.getUri()));
- }
+ UriData uriData = checkPermissionsAndCreateUriData(uri);
+ SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+ selectionBuilder.addClause(getPackageRestrictionClause());
+ return mVoicemailContentTable.update(uriData, values, selectionBuilder.build(),
+ selectionArgs);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
- UriData uriData = createUriData(uri);
- checkPackagePermission(uriData);
- final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- String combinedClause = concatenateClauses(selection, getPackageRestrictionClause(),
- getWhereClause(uriData), getCallTypeClause());
-
- // Delete all the files associated with this query. Once we've deleted the rows, there will
- // be no way left to get hold of the files.
- Cursor cursor = null;
- try {
- cursor = queryInternal(uriData, FILENAME_ONLY_PROJECTION, selection, selectionArgs,
- null);
- while (cursor.moveToNext()) {
- File file = new File(cursor.getString(0));
- if (file.exists()) {
- boolean success = file.delete();
- if (!success) {
- Log.e(TAG, "Failed to delete file: " + file.getAbsolutePath());
- }
- }
- }
- } finally {
- CloseUtils.closeQuietly(cursor);
- }
-
- // Now delete the rows themselves.
- int count = db.delete(VOICEMAILS_TABLE_NAME, combinedClause, selectionArgs);
- if (count > 0) {
- notifyChange(uri, Intent.ACTION_PROVIDER_CHANGED);
- }
- return count;
+ UriData uriData = checkPermissionsAndCreateUriData(uri);
+ SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+ selectionBuilder.addClause(getPackageRestrictionClause());
+ return mVoicemailContentTable.delete(uriData, selectionBuilder.build(), selectionArgs);
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
- UriData uriData = createUriData(uri);
- checkPackagePermission(uriData);
-
- // This relies on "_data" column to be populated with the file path.
- ParcelFileDescriptor openFileHelper = openFileHelper(uri, mode);
-
- // If the open succeeded, then update the file exists bit in the table.
- if (mode.contains("w")) {
- ContentValues contentValues = new ContentValues();
- contentValues.put(Voicemails.HAS_CONTENT, 1);
- update(uri, contentValues, null, null);
- }
-
- return openFileHelper;
- }
-
- /**
- * Notifies the content resolver and fires required broadcast intent(s) to notify about the
- * change.
- *
- * @param notificationUri The URI that got impacted due to the change. This is the URI that is
- * included in content resolver and broadcast intent notification.
- * @param intentActions List of intent actions that needs to be fired. A separate intent is
- * fired for each intent action.
- */
- private void notifyChange(Uri notificationUri, String... intentActions) {
- // Notify the observers.
- mContentResolver.notifyChange(notificationUri, null, true);
- String callingPackage = getCallingPackage();
- // Fire notification intents.
- for (String intentAction : intentActions) {
- // TODO: We can possibly be more intelligent here and send targeted intents based on
- // what voicemail permission the package has. If possible, here is what we would like to
- // do for a given broadcast intent -
- // 1) Send it to all packages that have READ_WRITE_ALL_VOICEMAIL permission.
- // 2) Send it to only the owner package that has just READ_WRITE_OWN_VOICEMAIL, if not
- // already sent in (1).
- List<ResolveInfo> resolveInfos = context().getPackageManager()
- .queryBroadcastReceivers(new Intent(intentAction, notificationUri), 0);
- for (ResolveInfo resolveInfo : resolveInfos) {
- String packageName = resolveInfo.activityInfo.packageName;
- Intent intent = new Intent(intentAction, notificationUri);
- intent.setPackage(packageName);
- intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
- callingPackage.equals(packageName));
- context().sendBroadcast(intent, Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
- }
- }
- }
-
- /** Generates a random file for storing audio data. */
- private String generateDataFile() {
- try {
- File dataDirectory = context().getDir(DATA_DIRECTORY, Context.MODE_PRIVATE);
- File voicemailFile = File.createTempFile("voicemail", "", dataDirectory);
- return voicemailFile.getAbsolutePath();
- } catch (IOException e) {
- // If we are unable to create a temporary file, something went horribly wrong.
- throw new RuntimeException("unable to create temp file", e);
- }
+ UriData uriData = checkPermissionsAndCreateUriData(uri);
+ // openFileHelper() relies on "_data" column to be populated with the file path.
+ return mVoicemailContentTable.openFile(uriData, mode, openFileHelper(uri, mode));
}
/**
* Decorates a URI by providing methods to get various properties from the URI.
*/
- private static class UriData {
+ public static class UriData {
private final Uri mUri;
private final String mId;
private final String mSourcePackage;
@@ -461,6 +166,107 @@
public final String getSourcePackage() {
return mSourcePackage;
}
+
+ /** 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)) {
+ case VOICEMAILS:
+ return new UriData(uri, null, sourcePackage);
+ case VOICEMAILS_ID:
+ return new UriData(uri, segments.get(1), sourcePackage);
+ case NO_MATCH:
+ throw new IllegalArgumentException("Invalid URI: " + uri);
+ default:
+ throw new IllegalStateException("Impossible, all cases are covered");
+ }
+ }
+ }
+
+ @Override
+ // VoicemailTable.DelegateHelper interface.
+ public void notifyChange(Uri notificationUri, String... intentActions) {
+ // Notify the observers.
+ mContentResolver.notifyChange(notificationUri, null, true);
+ String callingPackage = getCallingPackage();
+ // Fire notification intents.
+ for (String intentAction : intentActions) {
+ // TODO: We can possibly be more intelligent here and send targeted intents based on
+ // what voicemail permission the package has. If possible, here is what we would like to
+ // do for a given broadcast intent -
+ // 1) Send it to all packages that have READ_WRITE_ALL_VOICEMAIL permission.
+ // 2) Send it to only the owner package that has just READ_WRITE_OWN_VOICEMAIL, if not
+ // already sent in (1).
+ for (ComponentName component :
+ getBroadcastReceiverComponents(intentAction, notificationUri)) {
+ Intent intent = new Intent(intentAction, notificationUri);
+ intent.setComponent(component);
+ intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
+ callingPackage.equals(component.getPackageName()));
+ context().sendBroadcast(intent, Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
+ }
+ }
+ }
+
+ @Override
+ // 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)) {
+ 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));
+ }
+ }
+ // You must have access to the provider given in values.
+ if (!mVoicemailPermissions.callerHasFullAccess()) {
+ checkPackagesMatch(getCallingPackage(),
+ values.getAsString(VoicemailContract.SOURCE_PACKAGE_FIELD),
+ uriData.getUri());
+ }
+ }
+
+ private static TypedUriMatcherImpl<VoicemailUriType> createUriMatcher() {
+ return new TypedUriMatcherImpl<VoicemailUriType>(
+ VoicemailContract.AUTHORITY, VoicemailUriType.values());
+ }
+
+ /**
+ * Performs necessary voicemail permission checks common to all operations and returns
+ * the structured representation, {@link UriData}, of the supplied uri.
+ */
+ private UriData checkPermissionsAndCreateUriData(Uri uri) {
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
+ UriData uriData = UriData.createUriData(uri);
+ checkPackagePermission(uriData);
+ return uriData;
+ }
+
+ /**
+ * Checks that the callingProvider is same as voicemailProvider. Throws {@link
+ * SecurityException} if they don't match.
+ */
+ private final void checkPackagesMatch(String callingProvider, String voicemailProvider,
+ Uri uri) {
+ if (!voicemailProvider.equals(callingProvider)) {
+ String errorMsg = String.format("Permission denied for URI: %s\n. " +
+ "Provider %s cannot perform this operation for %s. Requires %s permission.",
+ uri, callingProvider, voicemailProvider,
+ Manifest.permission.READ_WRITE_ALL_VOICEMAIL);
+ throw new SecurityException(errorMsg);
+ }
}
/**
@@ -477,38 +283,14 @@
// 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 use /voicemail/provider/ query path instead.\nURI: %s",
+ "\nPlease set query parameter '%s' in the URI.\nURI: %s",
getCallingPackage(), Manifest.permission.READ_WRITE_ALL_VOICEMAIL,
- uriData.getUri()));
+ VoicemailContract.PARAM_KEY_SOURCE_PACKAGE, uriData.getUri()));
}
checkPackagesMatch(getCallingPackage(), uriData.getSourcePackage(), uriData.getUri());
}
}
- private static TypedUriMatcherImpl<VoicemailUriType> createUriMatcher() {
- return new TypedUriMatcherImpl<VoicemailUriType>(
- VoicemailContract.AUTHORITY, VoicemailUriType.values());
- }
-
- /** Get a {@link UriData} corresponding to a given uri. */
- private UriData createUriData(Uri uri) {
- List<String> segments = uri.getPathSegments();
- switch (createUriMatcher().match(uri)) {
- case VOICEMAILS:
- return new UriData(uri, null, null);
- case VOICEMAILS_ID:
- return new UriData(uri, segments.get(1), null);
- case VOICEMAILS_SOURCE:
- return new UriData(uri, null, segments.get(2));
- case VOICEMAILS_SOURCE_ID:
- return new UriData(uri, segments.get(3), segments.get(2));
- case NO_MATCH:
- throw new IllegalArgumentException("Invalid URI: " + uri);
- default:
- throw new IllegalStateException("Impossible, all cases are covered");
- }
- }
-
/**
* Gets the name of the calling package.
* <p>
@@ -556,10 +338,16 @@
return getEqualityClause(Voicemails.SOURCE_PACKAGE, getCallingPackage());
}
-
- /** Creates a clause to restrict the selection to only voicemail call type.*/
- private String getCallTypeClause() {
- return getEqualityClause(Calls.TYPE, String.valueOf(Calls.VOICEMAIL_TYPE));
+ /** Determines the components that can possibly receive the specified intent. */
+ protected List<ComponentName> getBroadcastReceiverComponents(String intentAction, Uri uri) {
+ Intent intent = new Intent(intentAction, uri);
+ List<ComponentName> receiverComponents = new ArrayList<ComponentName>();
+ // For broadcast receivers ResolveInfo.activityInfo is the one that is populated.
+ for (ResolveInfo resolveInfo :
+ context().getPackageManager().queryBroadcastReceivers(intent, 0)) {
+ ActivityInfo activityInfo = resolveInfo.activityInfo;
+ receiverComponents.add(new ComponentName(activityInfo.packageName, activityInfo.name));
+ }
+ return receiverComponents;
}
-
}
diff --git a/src/com/android/providers/contacts/VoicemailContentTable.java b/src/com/android/providers/contacts/VoicemailContentTable.java
new file mode 100644
index 0000000..8f6f3bf
--- /dev/null
+++ b/src/com/android/providers/contacts/VoicemailContentTable.java
@@ -0,0 +1,289 @@
+/*
+ * 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.checkForSupportedColumns;
+import static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses;
+import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
+
+import com.android.providers.contacts.VoicemailContentProvider.UriData;
+import com.android.providers.contacts.util.CloseUtils;
+
+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.CallLog.Calls;
+import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Voicemails;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Implementation of {@link VoicemailTable.Delegate} for the voicemail content table.
+ */
+public class VoicemailContentTable implements VoicemailTable.Delegate {
+ private static final String TAG = "VoicemailContentProvider";
+ // Voicemail projection map
+ private static final ProjectionMap sVoicemailProjectionMap = new ProjectionMap.Builder()
+ .add(Voicemails._ID)
+ .add(Voicemails.NUMBER)
+ .add(Voicemails.DATE)
+ .add(Voicemails.DURATION)
+ .add(Voicemails.NEW)
+ .add(Voicemails.STATE)
+ .add(Voicemails.SOURCE_DATA)
+ .add(Voicemails.SOURCE_PACKAGE)
+ .add(Voicemails.HAS_CONTENT)
+ .add(Voicemails.MIME_TYPE)
+ .add(Voicemails._DATA)
+ .build();
+
+ /** The private directory in which to store the data associated with the voicemail. */
+ private static final String DATA_DIRECTORY = "voicemail-data";
+
+ private static final String[] MIME_TYPE_ONLY_PROJECTION = new String[] { Voicemails.MIME_TYPE };
+ private static final String[] FILENAME_ONLY_PROJECTION = new String[] { Voicemails._DATA };
+
+ private final String mTableName;
+ private final SQLiteOpenHelper mDbHelper;
+ private final Context mContext;
+ private final VoicemailTable.DelegateHelper mDelegateHelper;
+
+ public VoicemailContentTable(String tableName, Context context, SQLiteOpenHelper dbHelper,
+ VoicemailTable.DelegateHelper contentProviderHelper) {
+ mTableName = tableName;
+ mContext = context;
+ mDbHelper = dbHelper;
+ mDelegateHelper = contentProviderHelper;
+ }
+
+ @Override
+ public int bulkInsert(UriData uriData, ContentValues[] valuesArray) {
+ int numInserted = 0;
+ for (ContentValues values : valuesArray) {
+ if (insertInternal(uriData, values, false) != null) {
+ numInserted++;
+ }
+ }
+ if (numInserted > 0) {
+ mDelegateHelper.notifyChange(uriData.getUri(), Intent.ACTION_PROVIDER_CHANGED);
+ }
+ return numInserted;
+ }
+
+ @Override
+ public Uri insert(UriData uriData, ContentValues values) {
+ return insertInternal(uriData, values, true);
+ }
+
+ private Uri insertInternal(UriData uriData, ContentValues values,
+ boolean sendProviderChangedNotification) {
+ checkForSupportedColumns(sVoicemailProjectionMap, values);
+ ContentValues copiedValues = new ContentValues(values);
+ checkInsertSupported(uriData);
+ mDelegateHelper.checkAndAddSourcePackageIntoValues(uriData, copiedValues);
+
+ // "_data" column is used by base ContentProvider's openFileHelper() to determine filename
+ // when Input/Output stream is requested to be opened.
+ copiedValues.put(Voicemails._DATA, generateDataFile());
+
+ // call type is always voicemail.
+ copiedValues.put(Calls.TYPE, Calls.VOICEMAIL_TYPE);
+
+ SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ long rowId = db.insert(mTableName, null, copiedValues);
+ if (rowId > 0) {
+ Uri newUri = ContentUris.withAppendedId(uriData.getUri(), rowId);
+ mDelegateHelper.notifyChange(newUri, VoicemailContract.ACTION_NEW_VOICEMAIL);
+ if (sendProviderChangedNotification) {
+ mDelegateHelper.notifyChange(newUri, Intent.ACTION_PROVIDER_CHANGED);
+ }
+ // Populate the 'voicemail_uri' field to be used by the call_log provider.
+ updateVoicemailUri(db, newUri);
+ return newUri;
+ }
+ return null;
+ }
+
+ private void checkInsertSupported(UriData uriData) {
+ if (uriData.hasId()) {
+ throw new UnsupportedOperationException(String.format(
+ "Cannot insert URI: %s. Inserted URIs should not contain an id.",
+ uriData.getUri()));
+ }
+ }
+
+ /** Generates a random file for storing audio data. */
+ private String generateDataFile() {
+ try {
+ File dataDirectory = mContext.getDir(DATA_DIRECTORY, Context.MODE_PRIVATE);
+ File voicemailFile = File.createTempFile("voicemail", "", dataDirectory);
+ return voicemailFile.getAbsolutePath();
+ } catch (IOException e) {
+ // If we are unable to create a temporary file, something went horribly wrong.
+ throw new RuntimeException("unable to create temp file", e);
+ }
+ }
+ private void updateVoicemailUri(SQLiteDatabase db, Uri newUri) {
+ ContentValues values = new ContentValues();
+ values.put(Calls.VOICEMAIL_URI, newUri.toString());
+ // 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);
+ }
+
+ @Override
+ public int delete(UriData uriData, String selection, String[] selectionArgs) {
+ final SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ String combinedClause = concatenateClauses(selection, getWhereClause(uriData),
+ getCallTypeClause());
+
+ // Delete all the files associated with this query. Once we've deleted the rows, there will
+ // be no way left to get hold of the files.
+ Cursor cursor = null;
+ try {
+ cursor = query(uriData, FILENAME_ONLY_PROJECTION, selection, selectionArgs, null);
+ while (cursor.moveToNext()) {
+ File file = new File(cursor.getString(0));
+ if (file.exists()) {
+ boolean success = file.delete();
+ if (!success) {
+ Log.e(TAG, "Failed to delete file: " + file.getAbsolutePath());
+ }
+ }
+ }
+ } finally {
+ CloseUtils.closeQuietly(cursor);
+ }
+
+ // Now delete the rows themselves.
+ 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(sVoicemailProjectionMap);
+ qb.setStrict(true);
+
+ String combinedClause = concatenateClauses(selection, getWhereClause(uriData),
+ getCallTypeClause());
+ SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ Cursor c = qb.query(db, projection, combinedClause, selectionArgs, null, null, sortOrder);
+ if (c != null) {
+ c.setNotificationUri(mContext.getContentResolver(), Voicemails.CONTENT_URI);
+ }
+ return c;
+ }
+
+ @Override
+ public int update(UriData uriData, ContentValues values, String selection,
+ String[] selectionArgs) {
+ checkForSupportedColumns(sVoicemailProjectionMap, values);
+ checkUpdateSupported(uriData);
+ 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),
+ getCallTypeClause());
+ int count = db.update(mTableName, values, combinedClause, selectionArgs);
+ if (count > 0) {
+ mDelegateHelper.notifyChange(uriData.getUri(), Intent.ACTION_PROVIDER_CHANGED);
+ }
+ return count;
+ }
+
+ private void checkUpdateSupported(UriData uriData) {
+ if (!uriData.hasId()) {
+ throw new UnsupportedOperationException(String.format(
+ "Cannot update URI: %s. Bulk update not supported", uriData.getUri()));
+ }
+ }
+
+ @Override
+ public String getType(UriData uriData) {
+ // TODO: DB lookup for the mime type may cause strict mode exception for the callers of
+ // getType(). See if this could be avoided.
+ if (uriData.hasId()) {
+ // An individual voicemail - so lookup the MIME type in the db.
+ return lookupMimeType(uriData);
+ }
+ // Not an individual voicemail - must be a directory listing type.
+ return Voicemails.DIR_TYPE;
+ }
+
+ /** Query the db for the MIME type of the given URI, called only from getType(). */
+ private String lookupMimeType(UriData uriData) {
+ Cursor cursor = null;
+ try {
+ // Use queryInternal, bypassing provider permission check. This is needed because
+ // getType() can be called from any application context (even without voicemail
+ // permissions) to know the MIME type of the URI. There is no security issue here as we
+ // do not expose any sensitive data through this interface.
+ cursor = query(uriData, MIME_TYPE_ONLY_PROJECTION, null, null, null);
+ if (cursor.moveToFirst()) {
+ return cursor.getString(cursor.getColumnIndex(Voicemails.MIME_TYPE));
+ }
+ } finally {
+ CloseUtils.closeQuietly(cursor);
+ }
+ return null;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(UriData uriData, String mode,
+ ParcelFileDescriptor openFileHelper) {
+ // 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));
+ }
+
+ /** Creates a clause to restrict the selection to only voicemail call type.*/
+ private String getCallTypeClause() {
+ return getEqualityClause(Calls.TYPE, String.valueOf(Calls.VOICEMAIL_TYPE));
+ }
+}
diff --git a/src/com/android/providers/contacts/VoicemailTable.java b/src/com/android/providers/contacts/VoicemailTable.java
new file mode 100644
index 0000000..d068775
--- /dev/null
+++ b/src/com/android/providers/contacts/VoicemailTable.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.providers.contacts.VoicemailContentProvider.UriData;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Defines interfaces for communication between voicemail content provider and voicemail table
+ * implementations.
+ */
+public interface VoicemailTable {
+ /**
+ * Interface that the voicemail content provider uses to delegate database level operations
+ * to the appropriate voicemail table implementation.
+ */
+ public interface Delegate {
+ public Uri insert(UriData uriData, ContentValues values);
+ public int bulkInsert(UriData uriData, ContentValues[] valuesArray);
+ public int delete(UriData uriData, String selection, String[] selectionArgs);
+ public Cursor query(UriData uriData, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder);
+ 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);
+ }
+
+ /**
+ * A helper interface that an implementation of {@link Delegate} uses to access common
+ * functionality across different voicemail tables.
+ */
+ public interface DelegateHelper {
+ /**
+ * Notifies the content resolver and fires required broadcast intent(s) to notify about the
+ * change.
+ *
+ * @param notificationUri The URI that got impacted due to the change. This is the URI that
+ * is included in content resolver and broadcast intent notification.
+ * @param intentActions List of intent actions that needs to be fired. A separate intent is
+ * fired for each intent action.
+ */
+ public void notifyChange(Uri notificationUri, String... intentActions);
+ /**
+ * Inserts source_package field into ContentValues. Used in insert operations.
+ */
+ public void checkAndAddSourcePackageIntoValues(UriData uriData, ContentValues values);
+ }
+}
\ 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 9e728a2..c3a33ab 100644
--- a/src/com/android/providers/contacts/VoicemailUriType.java
+++ b/src/com/android/providers/contacts/VoicemailUriType.java
@@ -23,9 +23,7 @@
enum VoicemailUriType implements UriType {
NO_MATCH(null),
VOICEMAILS("voicemail"),
- VOICEMAILS_ID("voicemail/#"),
- VOICEMAILS_SOURCE("voicemail/source/*"),
- VOICEMAILS_SOURCE_ID("voicemail/source/*/#");
+ VOICEMAILS_ID("voicemail/#");
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 c9d01c7..98498bd 100644
--- a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
+++ b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java
@@ -16,6 +16,7 @@
package com.android.providers.contacts;
+import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -24,8 +25,8 @@
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
-import android.provider.VoicemailContract;
import android.provider.CallLog.Calls;
+import android.provider.VoicemailContract;
import android.provider.VoicemailContract.Voicemails;
import android.test.MoreAsserts;
@@ -94,13 +95,13 @@
// Give away full permission, in case it was granted previously.
mActor.removePermissions(ALL_PERMISSION);
mActor.addPermissions(OWN_PERMISSION);
- mUseSourceUri = false;
+ mUseSourceUri = true;
}
private void setUpForFullPermission() {
mActor.addPermissions(OWN_PERMISSION);
mActor.addPermissions(ALL_PERMISSION);
- mUseSourceUri = true;
+ mUseSourceUri = false;
}
private void setUpForNoPermission() {
@@ -110,12 +111,8 @@
}
private Uri contentUri() {
- if (mUseSourceUri) {
- return VoicemailContract.CONTENT_URI;
- } else {
- return Uri.withAppendedPath(VoicemailContract.CONTENT_URI_SOURCE,
- mActor.packageName);
- }
+ return mUseSourceUri ?
+ Voicemails.buildSourceUri(mActor.packageName) : Voicemails.CONTENT_URI;
}
public void testInsert() throws Exception {
@@ -166,6 +163,54 @@
assertEquals(0, getCount(uri, null, null));
}
+ public void testGetType() throws Exception {
+ // voicemail with no MIME type.
+ ContentValues values = getDefaultVoicemailValues();
+ Uri uri = mResolver.insert(contentUri(), values);
+ assertEquals(null, mResolver.getType(uri));
+
+ values.put(Voicemails.MIME_TYPE, "foo/bar");
+ uri = mResolver.insert(contentUri(), values);
+ assertEquals("foo/bar", mResolver.getType(uri));
+
+ // base URIs.
+ assertEquals(Voicemails.DIR_TYPE, mResolver.getType(Voicemails.CONTENT_URI));
+ assertEquals(Voicemails.DIR_TYPE, mResolver.getType(Voicemails.buildSourceUri("foo")));
+ }
+
+ // Test to ensure that without full permission it is not possible to use the base uri (i.e. with
+ // no package URI specified).
+ public void testMustUsePackageUriWithoutFullPermission() {
+ setUpForOwnPermission();
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.insert(Voicemails.CONTENT_URI, getDefaultVoicemailValues());
+ }
+ });
+
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.update(Voicemails.CONTENT_URI, getDefaultVoicemailValues(), null, null);
+ }
+ });
+
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.query(Voicemails.CONTENT_URI, null, null, null, null);
+ }
+ });
+
+ EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
+ @Override
+ public void run() {
+ mResolver.delete(Voicemails.CONTENT_URI, null, null);
+ }
+ });
+ }
+
public void testPermissions_InsertAndQuery() {
setUpForFullPermission();
// Insert two records - one each with own and another package.
@@ -197,8 +242,9 @@
// 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);
+ mResolver.update(withSourcePackageParam(ownVoicemail),
+ getDefaultVoicemailValues(), null, null);
+ mResolver.delete(withSourcePackageParam(ownVoicemail), null, null);
// However, attempting to update or delete another-package's voicemail should fail.
EvenMoreAsserts.assertThrows(SecurityException.class, new Runnable() {
@@ -215,6 +261,12 @@
});
}
+ private Uri withSourcePackageParam(Uri uri) {
+ return uri.buildUpon()
+ .appendQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE, mActor.packageName)
+ .build();
+ }
+
// Test to ensure that all operations fail when no voicemail permission is granted.
public void testNoPermissions() {
setUpForNoPermission();
@@ -343,6 +395,10 @@
public File getDir(String name, int mode) {
return mDelgate.getDir(name, mode);
}
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission) {
+ mDelgate.sendOrderedBroadcast(intent, receiverPermission);
+ }
};
}
@@ -350,6 +406,14 @@
protected String getCallingPackage() {
return getContext().getPackageName();
}
+
+ @Override
+ protected List<ComponentName> getBroadcastReceiverComponents(String intentAction, Uri uri) {
+ List<ComponentName> broadcastReceiverComponents = new ArrayList<ComponentName>();
+ broadcastReceiverComponents.add(new ComponentName(
+ getContext().getPackageName(), "TestReceiverClass"));
+ return broadcastReceiverComponents;
+ }
}
/** Lazily construct the test directory when required. */
@@ -401,7 +465,7 @@
return new VvmProviderCalls() {
@Override
public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
- // TODO: Auto-generated method stub
+ // Do nothing for now.
}
@Override