auto import from //depot/cupcake/@135843
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
new file mode 100644
index 0000000..333a450
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -0,0 +1,341 @@
+/*
+ * 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);
+ }
+}