blob: 0101ece6d428353a3a1666fbc71cdd5d85d3d630 [file] [log] [blame]
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.providers.settings;
18
19import android.content.ContentProvider;
20import android.content.ContentUris;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.pm.PackageManager;
24import android.database.Cursor;
25import android.database.sqlite.SQLiteDatabase;
26import android.database.sqlite.SQLiteQueryBuilder;
27import android.media.RingtoneManager;
28import android.net.Uri;
29import android.os.ParcelFileDescriptor;
30import android.os.SystemProperties;
31import android.provider.DrmStore;
32import android.provider.MediaStore;
33import android.provider.Settings;
34import android.text.TextUtils;
35import android.util.Log;
36
37import java.io.FileNotFoundException;
38
39public class SettingsProvider extends ContentProvider {
40 private static final String TAG = "SettingsProvider";
41 private static final boolean LOCAL_LOGV = false;
42
James Wylder074da8f2009-02-25 08:38:31 -060043 protected DatabaseHelper mOpenHelper;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070044
45 /**
46 * Decode a content URL into the table, projection, and arguments
47 * used to access the corresponding database rows.
48 */
49 private static class SqlArguments {
50 public final String table;
51 public final String where;
52 public final String[] args;
53
54 /** Operate on existing rows. */
55 SqlArguments(Uri url, String where, String[] args) {
56 if (url.getPathSegments().size() == 1) {
57 this.table = url.getPathSegments().get(0);
58 this.where = where;
59 this.args = args;
60 } else if (url.getPathSegments().size() != 2) {
61 throw new IllegalArgumentException("Invalid URI: " + url);
62 } else if (!TextUtils.isEmpty(where)) {
63 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
64 } else {
65 this.table = url.getPathSegments().get(0);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -080066 if ("gservices".equals(this.table) || "system".equals(this.table)
67 || "secure".equals(this.table)) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070068 this.where = Settings.NameValueTable.NAME + "=?";
69 this.args = new String[] { url.getPathSegments().get(1) };
70 } else {
71 this.where = "_id=" + ContentUris.parseId(url);
72 this.args = null;
73 }
74 }
75 }
76
77 /** Insert new rows (no where clause allowed). */
78 SqlArguments(Uri url) {
79 if (url.getPathSegments().size() == 1) {
80 this.table = url.getPathSegments().get(0);
81 this.where = null;
82 this.args = null;
83 } else {
84 throw new IllegalArgumentException("Invalid URI: " + url);
85 }
86 }
87 }
88
89 /**
90 * Get the content URI of a row added to a table.
91 * @param tableUri of the entire table
92 * @param values found in the row
93 * @param rowId of the row
94 * @return the content URI for this particular row
95 */
96 private Uri getUriFor(Uri tableUri, ContentValues values, long rowId) {
97 if (tableUri.getPathSegments().size() != 1) {
98 throw new IllegalArgumentException("Invalid URI: " + tableUri);
99 }
100 String table = tableUri.getPathSegments().get(0);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800101 if ("gservices".equals(table) || "system".equals(table)
102 || "secure".equals(table)) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700103 String name = values.getAsString(Settings.NameValueTable.NAME);
104 return Uri.withAppendedPath(tableUri, name);
105 } else {
106 return ContentUris.withAppendedId(tableUri, rowId);
107 }
108 }
109
110 /**
111 * Send a notification when a particular content URI changes.
112 * Modify the system property used to communicate the version of
113 * this table, for tables which have such a property. (The Settings
114 * contract class uses these to provide client-side caches.)
115 * @param uri to send notifications for
116 */
117 private void sendNotify(Uri uri) {
118 // Update the system property *first*, so if someone is listening for
119 // a notification and then using the contract class to get their data,
120 // the system property will be updated and they'll get the new data.
121
122 String property = null, table = uri.getPathSegments().get(0);
123 if (table.equals("system")) {
124 property = Settings.System.SYS_PROP_SETTING_VERSION;
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800125 } else if (table.equals("secure")) {
126 property = Settings.Secure.SYS_PROP_SETTING_VERSION;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700127 } else if (table.equals("gservices")) {
128 property = Settings.Gservices.SYS_PROP_SETTING_VERSION;
129 }
130
131 if (property != null) {
132 long version = SystemProperties.getLong(property, 0) + 1;
133 if (LOCAL_LOGV) Log.v(TAG, "property: " + property + "=" + version);
134 SystemProperties.set(property, Long.toString(version));
135 }
136
137 // Now send the notification through the content framework.
138
139 String notify = uri.getQueryParameter("notify");
140 if (notify == null || "true".equals(notify)) {
141 getContext().getContentResolver().notifyChange(uri, null);
142 if (LOCAL_LOGV) Log.v(TAG, "notifying: " + uri);
143 } else {
144 if (LOCAL_LOGV) Log.v(TAG, "notification suppressed: " + uri);
145 }
146 }
147
148 /**
149 * Make sure the caller has permission to write this data.
150 * @param args supplied by the caller
151 * @throws SecurityException if the caller is forbidden to write.
152 */
153 private void checkWritePermissions(SqlArguments args) {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800154 if ("secure".equals(args.table) &&
155 getContext().checkCallingOrSelfPermission(
156 android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
157 PackageManager.PERMISSION_GRANTED) {
158 throw new SecurityException("Cannot write secure settings table");
159
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700160 // TODO: Move gservices into its own provider so we don't need this nonsense.
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800161 } else if ("gservices".equals(args.table) &&
162 getContext().checkCallingOrSelfPermission(
163 android.Manifest.permission.WRITE_GSERVICES) !=
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700164 PackageManager.PERMISSION_GRANTED) {
165 throw new SecurityException("Cannot write gservices table");
166 }
167 }
168
169 @Override
170 public boolean onCreate() {
171 mOpenHelper = new DatabaseHelper(getContext());
172 return true;
173 }
174
175 @Override
176 public Cursor query(Uri url, String[] select, String where, String[] whereArgs, String sort) {
177 SqlArguments args = new SqlArguments(url, where, whereArgs);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800178 // The favorites table was moved from this provider to a provider inside Home
179 // Home still need to query this table to upgrade from pre-cupcake builds
180 // However, a cupcake+ build with no data does not contain this table which will
181 // cause an exception in the SQL stack. The following line is a special case to
182 // let the caller of the query have a chance to recover and avoid the exception
183 if ("favorites".equals(args.table)) return null;
184
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700185 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
186 qb.setTables(args.table);
187
188 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
189 Cursor ret = qb.query(db, select, args.where, args.args, null, null, sort);
190 ret.setNotificationUri(getContext().getContentResolver(), url);
191 return ret;
192 }
193
194 @Override
195 public String getType(Uri url) {
196 // If SqlArguments supplies a where clause, then it must be an item
197 // (because we aren't supplying our own where clause).
198 SqlArguments args = new SqlArguments(url, null, null);
199 if (TextUtils.isEmpty(args.where)) {
200 return "vnd.android.cursor.dir/" + args.table;
201 } else {
202 return "vnd.android.cursor.item/" + args.table;
203 }
204 }
205
206 @Override
207 public int bulkInsert(Uri uri, ContentValues[] values) {
208 SqlArguments args = new SqlArguments(uri);
209 checkWritePermissions(args);
210
211 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
212 db.beginTransaction();
213 try {
214 int numValues = values.length;
215 for (int i = 0; i < numValues; i++) {
216 if (db.insert(args.table, null, values[i]) < 0) return 0;
217 if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + values[i]);
218 }
219 db.setTransactionSuccessful();
220 } finally {
221 db.endTransaction();
222 }
223
224 sendNotify(uri);
225 return values.length;
226 }
227
228 @Override
229 public Uri insert(Uri url, ContentValues initialValues) {
230 SqlArguments args = new SqlArguments(url);
231 checkWritePermissions(args);
232
233 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
234 final long rowId = db.insert(args.table, null, initialValues);
235 if (rowId <= 0) return null;
236
237 if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + initialValues);
238 url = getUriFor(url, initialValues, rowId);
239 sendNotify(url);
240 return url;
241 }
242
243 @Override
244 public int delete(Uri url, String where, String[] whereArgs) {
245 SqlArguments args = new SqlArguments(url, where, whereArgs);
246 checkWritePermissions(args);
247
248 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
249 int count = db.delete(args.table, args.where, args.args);
250 if (count > 0) sendNotify(url);
251 if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) deleted");
252 return count;
253 }
254
255 @Override
256 public int update(Uri url, ContentValues initialValues, String where, String[] whereArgs) {
257 SqlArguments args = new SqlArguments(url, where, whereArgs);
258 checkWritePermissions(args);
259
260 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
261 int count = db.update(args.table, initialValues, args.where, args.args);
262 if (count > 0) sendNotify(url);
263 if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) <- " + initialValues);
264 return count;
265 }
266
267 @Override
268 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
269
270 /*
271 * When a client attempts to openFile the default ringtone or
272 * notification setting Uri, we will proxy the call to the current
273 * default ringtone's Uri (if it is in the DRM or media provider).
274 */
275 int ringtoneType = RingtoneManager.getDefaultType(uri);
276 // Above call returns -1 if the Uri doesn't match a default type
277 if (ringtoneType != -1) {
278 Context context = getContext();
279
280 // Get the current value for the default sound
281 Uri soundUri = RingtoneManager.getActualDefaultRingtoneUri(context, ringtoneType);
282 if (soundUri == null) {
283 // Fallback on any valid ringtone Uri
284 soundUri = RingtoneManager.getValidRingtoneUri(context);
285 }
286
287 if (soundUri != null) {
288 // Only proxy the openFile call to drm or media providers
289 String authority = soundUri.getAuthority();
290 boolean isDrmAuthority = authority.equals(DrmStore.AUTHORITY);
291 if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY)) {
292
293 if (isDrmAuthority) {
294 try {
295 // Check DRM access permission here, since once we
296 // do the below call the DRM will be checking our
297 // permission, not our caller's permission
298 DrmStore.enforceAccessDrmPermission(context);
299 } catch (SecurityException e) {
300 throw new FileNotFoundException(e.getMessage());
301 }
302 }
303
304 return context.getContentResolver().openFileDescriptor(soundUri, mode);
305 }
306 }
307 }
308
309 return super.openFile(uri, mode);
310 }
311}