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 /src/com/android/providers/contacts | |
parent | 83a60d38646265694ab9cfa88b9601c201edb303 (diff) | |
download | packages_providers_ContactsProvider-9cf06e7bcb0be759f1c930412fd2e41eba4f5f03.zip packages_providers_ContactsProvider-9cf06e7bcb0be759f1c930412fd2e41eba4f5f03.tar.gz packages_providers_ContactsProvider-9cf06e7bcb0be759f1c930412fd2e41eba4f5f03.tar.bz2 |
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
Diffstat (limited to 'src/com/android/providers/contacts')
5 files changed, 264 insertions, 62 deletions
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java index 3a093c0..6cdde4c 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 8f6f3bf..0712a65 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 0000000..1f1fa8b --- /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 d068775..cab48fd 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 c3a33ab..eb1c3bd 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; |