summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java137
-rw-r--r--src/com/android/providers/contacts/VoicemailContentProvider.java60
-rw-r--r--src/com/android/providers/contacts/VoicemailPermissions.java91
-rw-r--r--src/com/android/providers/contacts/util/DbQueryUtils.java11
-rw-r--r--src/com/android/providers/contacts/util/SelectionBuilder.java63
5 files changed, 297 insertions, 65 deletions
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java
index e7a6351..b07a56d 100644
--- a/src/com/android/providers/contacts/CallLogProvider.java
+++ b/src/com/android/providers/contacts/CallLogProvider.java
@@ -17,9 +17,12 @@
package com.android.providers.contacts;
import static com.android.providers.contacts.util.DbQueryUtils.checkForSupportedColumns;
+import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
+import static com.android.providers.contacts.util.DbQueryUtils.getInequalityClause;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.util.DbQueryUtils;
+import com.android.providers.contacts.util.SelectionBuilder;
import android.content.ContentProvider;
import android.content.ContentUris;
@@ -33,6 +36,8 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
+import android.util.Log;
import java.util.HashMap;
import java.util.Set;
@@ -42,6 +47,31 @@ import java.util.Set;
*/
public class CallLogProvider extends ContentProvider {
+ /**
+ * An optional URI parameter for call_log operations which instructs the
+ * provider to allow the operation to be applied to voicemail records as well.
+ * <p> TYPE: Boolean
+ *
+ * <p> Using this parameter with a value true will result in a security
+ * error if the calling application does not have appropriate permissions
+ * to access voicemails.
+ */
+ // TODO: Move this to ContactsContract.Calls once we are happy with the changes.
+ public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails";
+
+ /**
+ * Content uri with {@link #ALLOW_VOICEMAILS_PARAM_KEY} set. This can directly
+ * be used to access call log entries that includes voicemail records.
+ */
+ // TODO: Move this to ContactsContract.Calls once we are happy with the changes.
+ public static Uri CONTENT_URI_WITH_VOICEMAIL = Calls.CONTENT_URI.buildUpon()
+ .appendQueryParameter(CallLogProvider.ALLOW_VOICEMAILS_PARAM_KEY, "true")
+ .build();
+
+ /** Selection clause to use to exclude voicemail records. */
+ private static final String EXCLUDE_VOICEMAIL_SELECTION = getInequalityClause(
+ Calls.TYPE, Integer.toString(Calls.VOICEMAIL_TYPE));
+
private static final int CALLS = 1;
private static final int CALLS_ID = 2;
@@ -77,6 +107,7 @@ public class CallLogProvider extends ContentProvider {
private DatabaseUtils.InsertHelper mCallsInserter;
private boolean mUseStrictPhoneNumberComparation;
private CountryMonitor mCountryMonitor;
+ private VoicemailPermissions mVoicemailPermissions;
@Override
public boolean onCreate() {
@@ -86,6 +117,7 @@ public class CallLogProvider extends ContentProvider {
context.getResources().getBoolean(
com.android.internal.R.bool.config_use_strict_phone_number_comparation);
mCountryMonitor = new CountryMonitor(context);
+ mVoicemailPermissions = new VoicemailPermissions(context);
return true;
}
@@ -102,18 +134,17 @@ public class CallLogProvider extends ContentProvider {
qb.setProjectionMap(sCallsProjectionMap);
qb.setStrict(true);
+ SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+ checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
+
int match = sURIMatcher.match(uri);
switch (match) {
case CALLS:
break;
case CALLS_ID: {
- try {
- Long id = Long.valueOf(uri.getPathSegments().get(1));
- qb.appendWhere(Calls._ID + "=" + id.toString());
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("Invalid call id in uri: " + uri, e);
- }
+ selectionBuilder.addClause(getEqualityClause(Calls._ID,
+ parseCallIdFromUri(uri)));
break;
}
@@ -130,7 +161,8 @@ public class CallLogProvider extends ContentProvider {
}
final SQLiteDatabase db = mDbHelper.getReadableDatabase();
- Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder, null);
+ Cursor c = qb.query(db, projection, selectionBuilder.build(), selectionArgs, null, null,
+ sortOrder, null);
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), CallLog.CONTENT_URI);
}
@@ -155,6 +187,13 @@ public class CallLogProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
checkForSupportedColumns(sCallsProjectionMap, values);
+ // Inserting a voicemail record through call_log requires the voicemail
+ // permission and also requires the additional voicemail param set.
+ if (hasVoicemailValue(values)) {
+ checkIsAllowVoicemailRequest(uri);
+ mVoicemailPermissions.checkCallerHasFullAccess();
+ }
+
// Inserted the current country code, so we know the country
// the number belongs to.
values.put(Calls.COUNTRY_ISO, getCurrentCountryIso());
@@ -172,26 +211,32 @@ public class CallLogProvider extends ContentProvider {
}
@Override
- public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
checkForSupportedColumns(sCallsProjectionMap, values);
+ // Request that involves changing record type to voicemail requires the
+ // voicemail param set in the uri.
+ if (hasVoicemailValue(values)) {
+ checkIsAllowVoicemailRequest(uri);
+ }
+
+ SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+ checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
+
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- String where;
- final int matchedUriId = sURIMatcher.match(url);
+ final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
case CALLS:
- where = selection;
break;
case CALLS_ID:
- where = DatabaseUtils.concatenateWhere(selection, Calls._ID + "="
- + url.getPathSegments().get(1));
+ selectionBuilder.addClause(getEqualityClause(Calls._ID, parseCallIdFromUri(uri)));
break;
default:
- throw new UnsupportedOperationException("Cannot update URL: " + url);
+ throw new UnsupportedOperationException("Cannot update URL: " + uri);
}
- int count = db.update(Tables.CALLS, values, where, selectionArgs);
+ int count = db.update(Tables.CALLS, values, selectionBuilder.build(), selectionArgs);
if (count > 0) {
notifyChange();
}
@@ -200,12 +245,14 @@ public class CallLogProvider extends ContentProvider {
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- final SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
+ checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
+ final SQLiteDatabase db = mDbHelper.getWritableDatabase();
final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
case CALLS:
- int count = db.delete(Tables.CALLS, selection, selectionArgs);
+ int count = db.delete(Tables.CALLS, selectionBuilder.build(), selectionArgs);
if (count > 0) {
notifyChange();
}
@@ -224,4 +271,60 @@ public class CallLogProvider extends ContentProvider {
protected String getCurrentCountryIso() {
return mCountryMonitor.getCountryIso();
}
+
+ private boolean hasVoicemailValue(ContentValues values) {
+ return values.containsKey(Calls.TYPE) &&
+ values.getAsInteger(Calls.TYPE).equals(Calls.VOICEMAIL_TYPE);
+ }
+
+ /**
+ * Checks if the supplied uri requests to include voicemails and take appropriate
+ * action.
+ * <p> If voicemail is requested, then check for voicemail permissions. Otherwise
+ * modify the selection to restrict to non-voicemail entries only.
+ */
+ private void checkVoicemailPermissionAndAddRestriction(Uri uri,
+ SelectionBuilder selectionBuilder) {
+ if (isAllowVoicemailRequest(uri)) {
+ mVoicemailPermissions.checkCallerHasFullAccess();
+ } else {
+ selectionBuilder.addClause(EXCLUDE_VOICEMAIL_SELECTION);
+ }
+ }
+
+ /**
+ * Determines if the supplied uri has the request to allow voicemails to be
+ * included.
+ */
+ private boolean isAllowVoicemailRequest(Uri uri) {
+ return uri.getBooleanQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, false);
+ }
+
+ /**
+ * Checks to ensure that the given uri has allow_voicemail set. Used by
+ * insert and update operations to check that ContentValues with voicemail
+ * call type must use the voicemail uri.
+ * @throws IllegalArgumentException if allow_voicemail is not set.
+ */
+ private void checkIsAllowVoicemailRequest(Uri uri) {
+ if (!isAllowVoicemailRequest(uri)) {
+ throw new IllegalArgumentException(
+ String.format("Uri %s cannot be used for voicemail record." +
+ " Please set '%s=true' in the uri.", uri, ALLOW_VOICEMAILS_PARAM_KEY));
+ }
+ }
+
+ /**
+ * Parses the call Id from the given uri, assuming that this is a uri that
+ * matches CALLS_ID. For other uri types the behaviour is undefined.
+ * @throws IllegalArgumentException if the id included in the Uri is not a valid long value.
+ */
+ private String parseCallIdFromUri(Uri uri) {
+ try {
+ Long id = Long.valueOf(uri.getPathSegments().get(1));
+ return id.toString();
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid call id in uri: " + uri, e);
+ }
+ }
}
diff --git a/src/com/android/providers/contacts/VoicemailContentProvider.java b/src/com/android/providers/contacts/VoicemailContentProvider.java
index 52903d1..07a9103 100644
--- a/src/com/android/providers/contacts/VoicemailContentProvider.java
+++ b/src/com/android/providers/contacts/VoicemailContentProvider.java
@@ -82,14 +82,14 @@ public class VoicemailContentProvider extends ContentProvider {
.build();
private ContentResolver mContentResolver;
private ContactsDatabaseHelper mDbHelper;
+ private VoicemailPermissions mVoicemailPermissions;
@Override
public boolean onCreate() {
Context context = context();
-
mContentResolver = context.getContentResolver();
mDbHelper = getDatabaseHelper(context);
-
+ mVoicemailPermissions = new VoicemailPermissions(context);
return true;
}
@@ -141,7 +141,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
UriData uriData = createUriData(uri);
checkPackagePermission(uriData);
return queryInternal(uriData, projection,
@@ -184,7 +184,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public int bulkInsert(Uri uri, ContentValues[] valuesArray) {
- checkCallerHasOwnPermission();
+ 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);
@@ -202,7 +202,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
return insertInternal(createUriData(uri), values, true);
}
@@ -266,7 +266,7 @@ public class VoicemailContentProvider extends ContentProvider {
}
}
// You must have access to the provider given in values.
- if (!callerHasFullPermission()) {
+ if (!mVoicemailPermissions.callerHasFullAccess()) {
checkPackagesMatch(getCallingPackage(), values.getAsString(Voicemails.SOURCE_PACKAGE),
uriData.getUri());
}
@@ -297,7 +297,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
UriData uriData = createUriData(uri);
checkPackagePermission(uriData);
checkForSupportedColumns(sVoicemailProjectionMap, values);
@@ -323,7 +323,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
UriData uriData = createUriData(uri);
checkPackagePermission(uriData);
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
@@ -359,7 +359,7 @@ public class VoicemailContentProvider extends ContentProvider {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- checkCallerHasOwnPermission();
+ mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
UriData uriData = createUriData(uri);
checkPackagePermission(uriData);
@@ -463,7 +463,7 @@ public class VoicemailContentProvider extends ContentProvider {
* @throws SecurityException if the check fails.
*/
private void checkPackagePermission(UriData uriData) {
- if (!callerHasFullPermission()) {
+ if (!mVoicemailPermissions.callerHasFullAccess()) {
if (!uriData.hasSourcePackage()) {
// You cannot have a match if this is not a provider uri.
throw new SecurityException(String.format(
@@ -525,11 +525,11 @@ public class VoicemailContentProvider extends ContentProvider {
// which one we return.
String bestSoFar = callerPackages[0];
for (String callerPackage : callerPackages) {
- if (hasPermission(callerPackage, Manifest.permission.READ_WRITE_ALL_VOICEMAIL)) {
+ if (mVoicemailPermissions.packageHasFullAccess(callerPackage)) {
// Full always wins, we can return early.
return callerPackage;
}
- if (hasPermission(callerPackage, Manifest.permission.READ_WRITE_OWN_VOICEMAIL)) {
+ if (mVoicemailPermissions.packageHasOwnVoicemailAccess(callerPackage)) {
bestSoFar = callerPackage;
}
}
@@ -537,45 +537,11 @@ public class VoicemailContentProvider extends ContentProvider {
}
/**
- * This check is made once only at every entry-point into this class from outside.
- *
- * @throws SecurityException if the caller does not have the voicemail source permission.
- */
- private void checkCallerHasOwnPermission() {
- if (!callerHasOwnPermission()) {
- throw new SecurityException("The caller must have permission: " +
- Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
- }
- }
-
- /** Determines if the calling process has own permission. */
- private boolean callerHasOwnPermission() {
- return callerHasPermission(Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
- }
-
- /** Determines if the calling process has full permission. */
- private boolean callerHasFullPermission() {
- return callerHasOwnPermission() &&
- callerHasPermission(Manifest.permission.READ_WRITE_ALL_VOICEMAIL);
- }
-
- /** Determines if the calling process has the given permission. */
- boolean callerHasPermission(String permission) {
- return context().checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
- }
-
- /** Determines if the given package has the given permission. */
- boolean hasPermission(String packageName, String permission) {
- return context().getPackageManager().checkPermission(permission, packageName)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
* Creates a clause to restrict the selection to the calling provider or null if the caller has
* access to all data.
*/
private String getPackageRestrictionClause() {
- if (callerHasFullPermission()) {
+ if (mVoicemailPermissions.callerHasFullAccess()) {
return null;
}
return getEqualityClause(Voicemails.SOURCE_PACKAGE, getCallingPackage());
diff --git a/src/com/android/providers/contacts/VoicemailPermissions.java b/src/com/android/providers/contacts/VoicemailPermissions.java
new file mode 100644
index 0000000..34dbced
--- /dev/null
+++ b/src/com/android/providers/contacts/VoicemailPermissions.java
@@ -0,0 +1,91 @@
+/*
+ * 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 android.content.Context;
+import android.content.pm.PackageManager;
+
+/**
+ * Provides method related to check various voicemail permissions under the
+ * specified context.
+ * <p> This is an immutable object.
+ */
+public class VoicemailPermissions {
+ private final Context mContext;
+
+ public VoicemailPermissions(Context context) {
+ mContext = context;
+ }
+
+ /** Determines if the calling process has access to its own voicemails. */
+ public boolean callerHasOwnVoicemailAccess() {
+ return callerHasPermission(Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
+ }
+
+ /** Determines if the calling process has access to all voicemails. */
+ public boolean callerHasFullAccess() {
+ return callerHasPermission(Manifest.permission.READ_WRITE_OWN_VOICEMAIL) &&
+ callerHasPermission(Manifest.permission.READ_WRITE_ALL_VOICEMAIL);
+ }
+
+ /**
+ * Checks that the caller has permissions to access its own voicemails.
+ *
+ * @throws SecurityException if the caller does not have the voicemail source permission.
+ */
+ public void checkCallerHasOwnVoicemailAccess() {
+ if (!callerHasOwnVoicemailAccess()) {
+ throw new SecurityException("The caller must have permission: " +
+ Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
+ }
+ }
+
+ /**
+ * Checks that the caller has permissions to access ALL voicemails.
+ *
+ * @throws SecurityException if the caller does not have the voicemail source permission.
+ */
+ public void checkCallerHasFullAccess() {
+ if (!callerHasFullAccess()) {
+ throw new SecurityException(String.format("The caller must have permissions %s AND %s",
+ Manifest.permission.READ_WRITE_OWN_VOICEMAIL,
+ Manifest.permission.READ_WRITE_ALL_VOICEMAIL));
+ }
+ }
+
+ /** Determines if the given package has access to its own voicemails. */
+ public boolean packageHasOwnVoicemailAccess(String packageName) {
+ return packageHasPermission(packageName, Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
+ }
+
+ /** Determines if the given package has full access. */
+ public boolean packageHasFullAccess(String packageName) {
+ return packageHasPermission(packageName, Manifest.permission.READ_WRITE_OWN_VOICEMAIL) &&
+ packageHasPermission(packageName, Manifest.permission.READ_WRITE_ALL_VOICEMAIL);
+ }
+
+ /** Determines if the given package has the given permission. */
+ private boolean packageHasPermission(String packageName, String permission) {
+ return mContext.getPackageManager().checkPermission(permission, packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /** Determines if the calling process has the given permission. */
+ private boolean callerHasPermission(String permission) {
+ return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
+ }
+}
diff --git a/src/com/android/providers/contacts/util/DbQueryUtils.java b/src/com/android/providers/contacts/util/DbQueryUtils.java
index 0045c59..58c8bb1 100644
--- a/src/com/android/providers/contacts/util/DbQueryUtils.java
+++ b/src/com/android/providers/contacts/util/DbQueryUtils.java
@@ -31,10 +31,19 @@ public class DbQueryUtils {
/** Returns a WHERE clause asserting equality of a field to a value. */
public static String getEqualityClause(String field, String value) {
+ return getClauseWithOperator(field, "=", value);
+ }
+
+ /** Returns a WHERE clause asserting in-equality of a field to a value. */
+ public static String getInequalityClause(String field, String value) {
+ return getClauseWithOperator(field, "!=", value);
+ }
+
+ private static String getClauseWithOperator(String field, String operator, String value) {
StringBuilder clause = new StringBuilder();
clause.append("(");
clause.append(field);
- clause.append(" = ");
+ clause.append(" ").append(operator).append(" ");
DatabaseUtils.appendEscapedSQLString(clause, value);
clause.append(")");
return clause.toString();
diff --git a/src/com/android/providers/contacts/util/SelectionBuilder.java b/src/com/android/providers/contacts/util/SelectionBuilder.java
new file mode 100644
index 0000000..14499e8
--- /dev/null
+++ b/src/com/android/providers/contacts/util/SelectionBuilder.java
@@ -0,0 +1,63 @@
+/*
+ * 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.util;
+
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builds a selection clause by concatenating several clauses with AND.
+ */
+public class SelectionBuilder {
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private final List<String> mWhereClauses;
+
+ /**
+ * @param baseSelection The base selection to start with. This is typically
+ * the user supplied selection arg. Pass null if no base selection is
+ * required.
+ */
+ public SelectionBuilder(String baseSelection) {
+ mWhereClauses = new ArrayList<String>();
+ addClause(baseSelection);
+ }
+
+ /**
+ * Adds a new clause to the selection. Nothing is added if the supplied clause
+ * is null or empty.
+ */
+ public SelectionBuilder addClause(String clause) {
+ if (!TextUtils.isEmpty(clause)) {
+ mWhereClauses.add(clause);
+ }
+ return this;
+ }
+
+ /**
+ * Returns a combined selection clause with AND of all clauses added using
+ * {@link #addClause(String)}. Returns null if no clause has been added or
+ * only null/empty clauses have been added till now.
+ */
+ public String build() {
+ if (mWhereClauses.size() == 0) {
+ return null;
+ }
+ return DbQueryUtils.concatenateClauses(mWhereClauses.toArray(EMPTY_STRING_ARRAY));
+ }
+}