blob: 333a450e0a80e2b68f1cc319114050a3a1770739 [file] [log] [blame]
/*
* Copyright (C) 2007 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.settings;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
import android.provider.DrmStore;
import android.provider.MediaStore;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import java.io.FileNotFoundException;
public class SettingsProvider extends ContentProvider {
private static final String TAG = "SettingsProvider";
private static final boolean LOCAL_LOGV = false;
private static final String TABLE_FAVORITES = "favorites";
private static final String TABLE_OLD_FAVORITES = "old_favorites";
private DatabaseHelper mOpenHelper;
/**
* Decode a content URL into the table, projection, and arguments
* used to access the corresponding database rows.
*/
private static class SqlArguments {
public String table;
public final String where;
public final String[] args;
/** Operate on existing rows. */
SqlArguments(Uri url, String where, String[] args) {
if (url.getPathSegments().size() == 1) {
this.table = url.getPathSegments().get(0);
this.where = where;
this.args = args;
} else if (url.getPathSegments().size() != 2) {
throw new IllegalArgumentException("Invalid URI: " + url);
} else if (!TextUtils.isEmpty(where)) {
throw new UnsupportedOperationException("WHERE clause not supported: " + url);
} else {
this.table = url.getPathSegments().get(0);
if ("gservices".equals(this.table) || "system".equals(this.table)
|| "secure".equals(this.table)) {
this.where = Settings.NameValueTable.NAME + "=?";
this.args = new String[] { url.getPathSegments().get(1) };
} else {
this.where = "_id=" + ContentUris.parseId(url);
this.args = null;
}
}
}
/** Insert new rows (no where clause allowed). */
SqlArguments(Uri url) {
if (url.getPathSegments().size() == 1) {
this.table = url.getPathSegments().get(0);
this.where = null;
this.args = null;
} else {
throw new IllegalArgumentException("Invalid URI: " + url);
}
}
}
/**
* Get the content URI of a row added to a table.
* @param tableUri of the entire table
* @param values found in the row
* @param rowId of the row
* @return the content URI for this particular row
*/
private Uri getUriFor(Uri tableUri, ContentValues values, long rowId) {
if (tableUri.getPathSegments().size() != 1) {
throw new IllegalArgumentException("Invalid URI: " + tableUri);
}
String table = tableUri.getPathSegments().get(0);
if ("gservices".equals(table) || "system".equals(table)
|| "secure".equals(table)) {
String name = values.getAsString(Settings.NameValueTable.NAME);
return Uri.withAppendedPath(tableUri, name);
} else {
return ContentUris.withAppendedId(tableUri, rowId);
}
}
/**
* Send a notification when a particular content URI changes.
* Modify the system property used to communicate the version of
* this table, for tables which have such a property. (The Settings
* contract class uses these to provide client-side caches.)
* @param uri to send notifications for
*/
private void sendNotify(Uri uri) {
// Update the system property *first*, so if someone is listening for
// a notification and then using the contract class to get their data,
// the system property will be updated and they'll get the new data.
String property = null, table = uri.getPathSegments().get(0);
if (table.equals("system")) {
property = Settings.System.SYS_PROP_SETTING_VERSION;
} else if (table.equals("secure")) {
property = Settings.Secure.SYS_PROP_SETTING_VERSION;
} else if (table.equals("gservices")) {
property = Settings.Gservices.SYS_PROP_SETTING_VERSION;
}
if (property != null) {
long version = SystemProperties.getLong(property, 0) + 1;
if (LOCAL_LOGV) Log.v(TAG, "property: " + property + "=" + version);
SystemProperties.set(property, Long.toString(version));
}
// Now send the notification through the content framework.
String notify = uri.getQueryParameter("notify");
if (notify == null || "true".equals(notify)) {
getContext().getContentResolver().notifyChange(uri, null);
if (LOCAL_LOGV) Log.v(TAG, "notifying: " + uri);
} else {
if (LOCAL_LOGV) Log.v(TAG, "notification suppressed: " + uri);
}
}
/**
* Make sure the caller has permission to write this data.
* @param args supplied by the caller
* @throws SecurityException if the caller is forbidden to write.
*/
private void checkWritePermissions(SqlArguments args) {
if ("secure".equals(args.table) &&
getContext().checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Cannot write secure settings table");
// TODO: Move gservices into its own provider so we don't need this nonsense.
} else if ("gservices".equals(args.table) &&
getContext().checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_GSERVICES) !=
PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Cannot write gservices table");
}
}
@Override
public boolean onCreate() {
mOpenHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri url, String[] select, String where, String[] whereArgs, String sort) {
SqlArguments args = new SqlArguments(url, where, whereArgs);
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
// The favorites table was moved from this provider to a provider inside Home
// Home still need to query this table to upgrade from pre-cupcake builds
// However, a cupcake+ build with no data does not contain this table which will
// cause an exception in the SQL stack. The following line is a special case to
// let the caller of the query have a chance to recover and avoid the exception
if (TABLE_FAVORITES.equals(args.table)) {
return null;
} else if (TABLE_OLD_FAVORITES.equals(args.table)) {
args.table = TABLE_FAVORITES;
Cursor cursor = db.rawQuery("PRAGMA table_info(favorites);", null);
if (cursor != null) {
boolean exists = cursor.getCount() > 0;
cursor.close();
if (!exists) return null;
} else {
return null;
}
}
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(args.table);
Cursor ret = qb.query(db, select, args.where, args.args, null, null, sort);
ret.setNotificationUri(getContext().getContentResolver(), url);
return ret;
}
@Override
public String getType(Uri url) {
// If SqlArguments supplies a where clause, then it must be an item
// (because we aren't supplying our own where clause).
SqlArguments args = new SqlArguments(url, null, null);
if (TextUtils.isEmpty(args.where)) {
return "vnd.android.cursor.dir/" + args.table;
} else {
return "vnd.android.cursor.item/" + args.table;
}
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
SqlArguments args = new SqlArguments(uri);
if (TABLE_FAVORITES.equals(args.table)) {
return 0;
}
checkWritePermissions(args);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
int numValues = values.length;
for (int i = 0; i < numValues; i++) {
if (db.insert(args.table, null, values[i]) < 0) return 0;
if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + values[i]);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
sendNotify(uri);
return values.length;
}
@Override
public Uri insert(Uri url, ContentValues initialValues) {
SqlArguments args = new SqlArguments(url);
if (TABLE_FAVORITES.equals(args.table)) {
return null;
}
checkWritePermissions(args);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final long rowId = db.insert(args.table, null, initialValues);
if (rowId <= 0) return null;
if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + initialValues);
url = getUriFor(url, initialValues, rowId);
sendNotify(url);
return url;
}
@Override
public int delete(Uri url, String where, String[] whereArgs) {
SqlArguments args = new SqlArguments(url, where, whereArgs);
if (TABLE_FAVORITES.equals(args.table)) {
return 0;
} else if (TABLE_OLD_FAVORITES.equals(args.table)) {
args.table = TABLE_FAVORITES;
}
checkWritePermissions(args);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count = db.delete(args.table, args.where, args.args);
if (count > 0) sendNotify(url);
if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) deleted");
return count;
}
@Override
public int update(Uri url, ContentValues initialValues, String where, String[] whereArgs) {
SqlArguments args = new SqlArguments(url, where, whereArgs);
if (TABLE_FAVORITES.equals(args.table)) {
return 0;
}
checkWritePermissions(args);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count = db.update(args.table, initialValues, args.where, args.args);
if (count > 0) sendNotify(url);
if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) <- " + initialValues);
return count;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
/*
* When a client attempts to openFile the default ringtone or
* notification setting Uri, we will proxy the call to the current
* default ringtone's Uri (if it is in the DRM or media provider).
*/
int ringtoneType = RingtoneManager.getDefaultType(uri);
// Above call returns -1 if the Uri doesn't match a default type
if (ringtoneType != -1) {
Context context = getContext();
// Get the current value for the default sound
Uri soundUri = RingtoneManager.getActualDefaultRingtoneUri(context, ringtoneType);
if (soundUri == null) {
// Fallback on any valid ringtone Uri
soundUri = RingtoneManager.getValidRingtoneUri(context);
}
if (soundUri != null) {
// Only proxy the openFile call to drm or media providers
String authority = soundUri.getAuthority();
boolean isDrmAuthority = authority.equals(DrmStore.AUTHORITY);
if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY)) {
if (isDrmAuthority) {
try {
// Check DRM access permission here, since once we
// do the below call the DRM will be checking our
// permission, not our caller's permission
DrmStore.enforceAccessDrmPermission(context);
} catch (SecurityException e) {
throw new FileNotFoundException(e.getMessage());
}
}
return context.getContentResolver().openFileDescriptor(soundUri, mode);
}
}
}
return super.openFile(uri, mode);
}
}