1 /*
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 
17 package com.android.providers.settings;
18 
19 import java.io.FileNotFoundException;
20 import java.security.SecureRandom;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.atomic.AtomicBoolean;
26 import java.util.concurrent.atomic.AtomicInteger;
27 
28 import android.app.ActivityManager;
29 import android.app.AppOpsManager;
30 import android.app.backup.BackupManager;
31 import android.content.BroadcastReceiver;
32 import android.content.ContentProvider;
33 import android.content.ContentUris;
34 import android.content.ContentValues;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.content.pm.PackageManager;
39 import android.content.pm.UserInfo;
40 import android.database.AbstractCursor;
41 import android.database.Cursor;
42 import android.database.sqlite.SQLiteDatabase;
43 import android.database.sqlite.SQLiteException;
44 import android.database.sqlite.SQLiteQueryBuilder;
45 import android.net.Uri;
46 import android.os.Binder;
47 import android.os.Bundle;
48 import android.os.DropBoxManager;
49 import android.os.FileObserver;
50 import android.os.ParcelFileDescriptor;
51 import android.os.Process;
52 import android.os.SystemProperties;
53 import android.os.UserHandle;
54 import android.os.UserManager;
55 import android.provider.Settings;
56 import android.provider.Settings.Secure;
57 import android.text.TextUtils;
58 import android.util.Log;
59 import android.util.LruCache;
60 import android.util.Slog;
61 import android.util.SparseArray;
62 
63 public class SettingsProvider extends ContentProvider {
64     private static final String TAG = "SettingsProvider";
65     private static final boolean LOCAL_LOGV = false;
66 
67     private static final boolean USER_CHECK_THROWS = true;
68 
69     private static final String TABLE_SYSTEM = "system";
70     private static final String TABLE_SECURE = "secure";
71     private static final String TABLE_GLOBAL = "global";
72     private static final String TABLE_FAVORITES = "favorites";
73     private static final String TABLE_OLD_FAVORITES = "old_favorites";
74 
75     private static final String[] COLUMN_VALUE = new String[] { "value" };
76 
77     // Caches for each user's settings, access-ordered for acting as LRU.
78     // Guarded by themselves.
79     private static final int MAX_CACHE_ENTRIES = 200;
80     private static final SparseArray<SettingsCache> sSystemCaches
81             = new SparseArray<SettingsCache>();
82     private static final SparseArray<SettingsCache> sSecureCaches
83             = new SparseArray<SettingsCache>();
84     private static final SettingsCache sGlobalCache = new SettingsCache(TABLE_GLOBAL);
85 
86     // The count of how many known (handled by SettingsProvider)
87     // database mutations are currently being handled for this user.
88     // Used by file observers to not reload the database when it's ourselves
89     // modifying it.
90     private static final SparseArray<AtomicInteger> sKnownMutationsInFlight
91             = new SparseArray<AtomicInteger>();
92 
93     // Each defined user has their own settings
94     protected final SparseArray<DatabaseHelper> mOpenHelpers = new SparseArray<DatabaseHelper>();
95 
96     // Keep the list of managed profiles synced here
97     private List<UserInfo> mManagedProfiles = null;
98 
99     // Over this size we don't reject loading or saving settings but
100     // we do consider them broken/malicious and don't keep them in
101     // memory at least:
102     private static final int MAX_CACHE_ENTRY_SIZE = 500;
103 
104     private static final Bundle NULL_SETTING = Bundle.forPair("value", null);
105 
106     // Used as a sentinel value in an instance equality test when we
107     // want to cache the existence of a key, but not store its value.
108     private static final Bundle TOO_LARGE_TO_CACHE_MARKER = Bundle.forPair("_dummy", null);
109 
110     private UserManager mUserManager;
111     private BackupManager mBackupManager;
112 
113     /**
114      * Settings which need to be treated as global/shared in multi-user environments.
115      */
116     static final HashSet<String> sSecureGlobalKeys;
117     static final HashSet<String> sSystemGlobalKeys;
118 
119     // Settings that cannot be modified if associated user restrictions are enabled.
120     static final Map<String, String> sRestrictedKeys;
121 
122     private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
123 
124     static final HashSet<String> sSecureCloneToManagedKeys;
125     static final HashSet<String> sSystemCloneToManagedKeys;
126 
127     static {
128         // Keys (name column) from the 'secure' table that are now in the owner user's 'global'
129         // table, shared across all users
130         // These must match Settings.Secure.MOVED_TO_GLOBAL
131         sSecureGlobalKeys = new HashSet<String>();
132         Settings.Secure.getMovedKeys(sSecureGlobalKeys);
133 
134         // Keys from the 'system' table now moved to 'global'
135         // These must match Settings.System.MOVED_TO_GLOBAL
136         sSystemGlobalKeys = new HashSet<String>();
137         Settings.System.getNonLegacyMovedKeys(sSystemGlobalKeys);
138 
139         sRestrictedKeys = new HashMap<String, String>();
sRestrictedKeys.put(Settings.Secure.LOCATION_MODE, UserManager.DISALLOW_SHARE_LOCATION)140         sRestrictedKeys.put(Settings.Secure.LOCATION_MODE, UserManager.DISALLOW_SHARE_LOCATION);
sRestrictedKeys.put(Settings.Secure.LOCATION_PROVIDERS_ALLOWED, UserManager.DISALLOW_SHARE_LOCATION)141         sRestrictedKeys.put(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
142                 UserManager.DISALLOW_SHARE_LOCATION);
sRestrictedKeys.put(Settings.Secure.INSTALL_NON_MARKET_APPS, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)143         sRestrictedKeys.put(Settings.Secure.INSTALL_NON_MARKET_APPS,
144                 UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
sRestrictedKeys.put(Settings.Global.ADB_ENABLED, UserManager.DISALLOW_DEBUGGING_FEATURES)145         sRestrictedKeys.put(Settings.Global.ADB_ENABLED, UserManager.DISALLOW_DEBUGGING_FEATURES);
sRestrictedKeys.put(Settings.Global.PACKAGE_VERIFIER_ENABLE, UserManager.ENSURE_VERIFY_APPS)146         sRestrictedKeys.put(Settings.Global.PACKAGE_VERIFIER_ENABLE,
147                 UserManager.ENSURE_VERIFY_APPS);
sRestrictedKeys.put(Settings.Global.PREFERRED_NETWORK_MODE, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)148         sRestrictedKeys.put(Settings.Global.PREFERRED_NETWORK_MODE,
149                 UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
150 
151         sSecureCloneToManagedKeys = new HashSet<String>();
152         for (int i = 0; i < Settings.Secure.CLONE_TO_MANAGED_PROFILE.length; i++) {
153             sSecureCloneToManagedKeys.add(Settings.Secure.CLONE_TO_MANAGED_PROFILE[i]);
154         }
155         sSystemCloneToManagedKeys = new HashSet<String>();
156         for (int i = 0; i < Settings.System.CLONE_TO_MANAGED_PROFILE.length; i++) {
157             sSystemCloneToManagedKeys.add(Settings.System.CLONE_TO_MANAGED_PROFILE[i]);
158         }
159     }
160 
settingMovedToGlobal(final String name)161     private boolean settingMovedToGlobal(final String name) {
162         return sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name);
163     }
164 
165     /**
166      * Decode a content URL into the table, projection, and arguments
167      * used to access the corresponding database rows.
168      */
169     private static class SqlArguments {
170         public String table;
171         public final String where;
172         public final String[] args;
173 
174         /** Operate on existing rows. */
SqlArguments(Uri url, String where, String[] args)175         SqlArguments(Uri url, String where, String[] args) {
176             if (url.getPathSegments().size() == 1) {
177                 // of the form content://settings/secure, arbitrary where clause
178                 this.table = url.getPathSegments().get(0);
179                 if (!DatabaseHelper.isValidTable(this.table)) {
180                     throw new IllegalArgumentException("Bad root path: " + this.table);
181                 }
182                 this.where = where;
183                 this.args = args;
184             } else if (url.getPathSegments().size() != 2) {
185                 throw new IllegalArgumentException("Invalid URI: " + url);
186             } else if (!TextUtils.isEmpty(where)) {
187                 throw new UnsupportedOperationException("WHERE clause not supported: " + url);
188             } else {
189                 // of the form content://settings/secure/element_name, no where clause
190                 this.table = url.getPathSegments().get(0);
191                 if (!DatabaseHelper.isValidTable(this.table)) {
192                     throw new IllegalArgumentException("Bad root path: " + this.table);
193                 }
194                 if (TABLE_SYSTEM.equals(this.table) || TABLE_SECURE.equals(this.table) ||
195                     TABLE_GLOBAL.equals(this.table)) {
196                     this.where = Settings.NameValueTable.NAME + "=?";
197                     final String name = url.getPathSegments().get(1);
198                     this.args = new String[] { name };
199                     // Rewrite the table for known-migrated names
200                     if (TABLE_SYSTEM.equals(this.table) || TABLE_SECURE.equals(this.table)) {
201                         if (sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name)) {
202                             this.table = TABLE_GLOBAL;
203                         }
204                     }
205                 } else {
206                     // of the form content://bookmarks/19
207                     this.where = "_id=" + ContentUris.parseId(url);
208                     this.args = null;
209                 }
210             }
211         }
212 
213         /** Insert new rows (no where clause allowed). */
SqlArguments(Uri url)214         SqlArguments(Uri url) {
215             if (url.getPathSegments().size() == 1) {
216                 this.table = url.getPathSegments().get(0);
217                 if (!DatabaseHelper.isValidTable(this.table)) {
218                     throw new IllegalArgumentException("Bad root path: " + this.table);
219                 }
220                 this.where = null;
221                 this.args = null;
222             } else {
223                 throw new IllegalArgumentException("Invalid URI: " + url);
224             }
225         }
226     }
227 
228     /**
229      * Get the content URI of a row added to a table.
230      * @param tableUri of the entire table
231      * @param values found in the row
232      * @param rowId of the row
233      * @return the content URI for this particular row
234      */
getUriFor(Uri tableUri, ContentValues values, long rowId)235     private Uri getUriFor(Uri tableUri, ContentValues values, long rowId) {
236         if (tableUri.getPathSegments().size() != 1) {
237             throw new IllegalArgumentException("Invalid URI: " + tableUri);
238         }
239         String table = tableUri.getPathSegments().get(0);
240         if (TABLE_SYSTEM.equals(table) ||
241                 TABLE_SECURE.equals(table) ||
242                 TABLE_GLOBAL.equals(table)) {
243             String name = values.getAsString(Settings.NameValueTable.NAME);
244             return Uri.withAppendedPath(tableUri, name);
245         } else {
246             return ContentUris.withAppendedId(tableUri, rowId);
247         }
248     }
249 
250     /**
251      * Send a notification when a particular content URI changes.
252      * Modify the system property used to communicate the version of
253      * this table, for tables which have such a property.  (The Settings
254      * contract class uses these to provide client-side caches.)
255      * @param uri to send notifications for
256      */
sendNotify(Uri uri, int userHandle)257     private void sendNotify(Uri uri, int userHandle) {
258         // Update the system property *first*, so if someone is listening for
259         // a notification and then using the contract class to get their data,
260         // the system property will be updated and they'll get the new data.
261 
262         boolean backedUpDataChanged = false;
263         String property = null, table = uri.getPathSegments().get(0);
264         final boolean isGlobal = table.equals(TABLE_GLOBAL);
265         if (table.equals(TABLE_SYSTEM)) {
266             property = Settings.System.SYS_PROP_SETTING_VERSION;
267             backedUpDataChanged = true;
268         } else if (table.equals(TABLE_SECURE)) {
269             property = Settings.Secure.SYS_PROP_SETTING_VERSION;
270             backedUpDataChanged = true;
271         } else if (isGlobal) {
272             property = Settings.Global.SYS_PROP_SETTING_VERSION;    // this one is global
273             backedUpDataChanged = true;
274         }
275 
276         if (property != null) {
277             long version = SystemProperties.getLong(property, 0) + 1;
278             if (LOCAL_LOGV) Log.v(TAG, "property: " + property + "=" + version);
279             SystemProperties.set(property, Long.toString(version));
280         }
281 
282         // Inform the backup manager about a data change
283         if (backedUpDataChanged) {
284             mBackupManager.dataChanged();
285         }
286         // Now send the notification through the content framework.
287 
288         String notify = uri.getQueryParameter("notify");
289         if (notify == null || "true".equals(notify)) {
290             final int notifyTarget = isGlobal ? UserHandle.USER_ALL : userHandle;
291             final long oldId = Binder.clearCallingIdentity();
292             try {
293                 getContext().getContentResolver().notifyChange(uri, null, true, notifyTarget);
294             } finally {
295                 Binder.restoreCallingIdentity(oldId);
296             }
297             if (LOCAL_LOGV) Log.v(TAG, "notifying for " + notifyTarget + ": " + uri);
298         } else {
299             if (LOCAL_LOGV) Log.v(TAG, "notification suppressed: " + uri);
300         }
301     }
302 
303     /**
304      * Make sure the caller has permission to write this data.
305      * @param args supplied by the caller
306      * @throws SecurityException if the caller is forbidden to write.
307      */
checkWritePermissions(SqlArguments args)308     private void checkWritePermissions(SqlArguments args) {
309         if ((TABLE_SECURE.equals(args.table) || TABLE_GLOBAL.equals(args.table)) &&
310             getContext().checkCallingOrSelfPermission(
311                     android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
312             PackageManager.PERMISSION_GRANTED) {
313             throw new SecurityException(
314                     String.format("Permission denial: writing to secure settings requires %1$s",
315                                   android.Manifest.permission.WRITE_SECURE_SETTINGS));
316         }
317     }
318 
checkUserRestrictions(String setting, int userId)319     private void checkUserRestrictions(String setting, int userId) {
320         String userRestriction = sRestrictedKeys.get(setting);
321         if (!TextUtils.isEmpty(userRestriction)
322             && mUserManager.hasUserRestriction(userRestriction, new UserHandle(userId))) {
323             throw new SecurityException(
324                     "Permission denial: user is restricted from changing this setting.");
325         }
326     }
327 
328     // FileObserver for external modifications to the database file.
329     // Note that this is for platform developers only with
330     // userdebug/eng builds who should be able to tinker with the
331     // sqlite database out from under the SettingsProvider, which is
332     // normally the exclusive owner of the database.  But we keep this
333     // enabled all the time to minimize development-vs-user
334     // differences in testing.
335     private static SparseArray<SettingsFileObserver> sObserverInstances
336             = new SparseArray<SettingsFileObserver>();
337     private class SettingsFileObserver extends FileObserver {
338         private final AtomicBoolean mIsDirty = new AtomicBoolean(false);
339         private final int mUserHandle;
340         private final String mPath;
341 
SettingsFileObserver(int userHandle, String path)342         public SettingsFileObserver(int userHandle, String path) {
343             super(path, FileObserver.CLOSE_WRITE |
344                   FileObserver.CREATE | FileObserver.DELETE |
345                   FileObserver.MOVED_TO | FileObserver.MODIFY);
346             mUserHandle = userHandle;
347             mPath = path;
348         }
349 
onEvent(int event, String path)350         public void onEvent(int event, String path) {
351             final AtomicInteger mutationCount;
352             synchronized (SettingsProvider.this) {
353                 mutationCount = sKnownMutationsInFlight.get(mUserHandle);
354             }
355             if (mutationCount != null && mutationCount.get() > 0) {
356                 // our own modification.
357                 return;
358             }
359             Log.d(TAG, "User " + mUserHandle + " external modification to " + mPath
360                     + "; event=" + event);
361             if (!mIsDirty.compareAndSet(false, true)) {
362                 // already handled. (we get a few update events
363                 // during an sqlite write)
364                 return;
365             }
366             Log.d(TAG, "User " + mUserHandle + " updating our caches for " + mPath);
367             fullyPopulateCaches(mUserHandle);
368             mIsDirty.set(false);
369         }
370     }
371 
372     @Override
onCreate()373     public boolean onCreate() {
374         mBackupManager = new BackupManager(getContext());
375         mUserManager = UserManager.get(getContext());
376 
377         setAppOps(AppOpsManager.OP_NONE, AppOpsManager.OP_WRITE_SETTINGS);
378         establishDbTracking(UserHandle.USER_OWNER);
379 
380         IntentFilter userFilter = new IntentFilter();
381         userFilter.addAction(Intent.ACTION_USER_REMOVED);
382         userFilter.addAction(Intent.ACTION_USER_ADDED);
383         getContext().registerReceiver(new BroadcastReceiver() {
384             @Override
385             public void onReceive(Context context, Intent intent) {
386                 final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
387                         UserHandle.USER_OWNER);
388                 if (intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
389                     onUserRemoved(userHandle);
390                 } else if (intent.getAction().equals(Intent.ACTION_USER_ADDED)) {
391                     onProfilesChanged();
392                 }
393             }
394         }, userFilter);
395 
396         onProfilesChanged();
397 
398         return true;
399     }
400 
onUserRemoved(int userHandle)401     void onUserRemoved(int userHandle) {
402         synchronized (this) {
403             // the db file itself will be deleted automatically, but we need to tear down
404             // our caches and other internal bookkeeping.
405             FileObserver observer = sObserverInstances.get(userHandle);
406             if (observer != null) {
407                 observer.stopWatching();
408                 sObserverInstances.delete(userHandle);
409             }
410 
411             mOpenHelpers.delete(userHandle);
412             sSystemCaches.delete(userHandle);
413             sSecureCaches.delete(userHandle);
414             sKnownMutationsInFlight.delete(userHandle);
415             onProfilesChanged();
416         }
417     }
418 
419     /**
420      * Updates the list of managed profiles. It assumes that only the primary user
421      * can have managed profiles. Modify this code if that changes in the future.
422      */
onProfilesChanged()423     void onProfilesChanged() {
424         synchronized (this) {
425             mManagedProfiles = mUserManager.getProfiles(UserHandle.USER_OWNER);
426             if (mManagedProfiles != null) {
427                 // Remove the primary user from the list
428                 for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
429                     if (mManagedProfiles.get(i).id == UserHandle.USER_OWNER) {
430                         mManagedProfiles.remove(i);
431                     }
432                 }
433                 // If there are no managed profiles, reset the variable
434                 if (mManagedProfiles.size() == 0) {
435                     mManagedProfiles = null;
436                 }
437             }
438             if (LOCAL_LOGV) {
439                 Slog.d(TAG, "Managed Profiles = " + mManagedProfiles);
440             }
441         }
442     }
443 
establishDbTracking(int userHandle)444     private void establishDbTracking(int userHandle) {
445         if (LOCAL_LOGV) {
446             Slog.i(TAG, "Installing settings db helper and caches for user " + userHandle);
447         }
448 
449         DatabaseHelper dbhelper;
450 
451         synchronized (this) {
452             dbhelper = mOpenHelpers.get(userHandle);
453             if (dbhelper == null) {
454                 dbhelper = new DatabaseHelper(getContext(), userHandle);
455                 mOpenHelpers.append(userHandle, dbhelper);
456 
457                 sSystemCaches.append(userHandle, new SettingsCache(TABLE_SYSTEM));
458                 sSecureCaches.append(userHandle, new SettingsCache(TABLE_SECURE));
459                 sKnownMutationsInFlight.append(userHandle, new AtomicInteger(0));
460             }
461         }
462 
463         // Initialization of the db *outside* the locks.  It's possible that racing
464         // threads might wind up here, the second having read the cache entries
465         // written by the first, but that's benign: the SQLite helper implementation
466         // manages concurrency itself, and it's important that we not run the db
467         // initialization with any of our own locks held, so we're fine.
468         SQLiteDatabase db = dbhelper.getWritableDatabase();
469 
470         // Watch for external modifications to the database files,
471         // keeping our caches in sync.  We synchronize the observer set
472         // separately, and of course it has to run after the db file
473         // itself was set up by the DatabaseHelper.
474         synchronized (sObserverInstances) {
475             if (sObserverInstances.get(userHandle) == null) {
476                 SettingsFileObserver observer = new SettingsFileObserver(userHandle, db.getPath());
477                 sObserverInstances.append(userHandle, observer);
478                 observer.startWatching();
479             }
480         }
481 
482         ensureAndroidIdIsSet(userHandle);
483 
484         startAsyncCachePopulation(userHandle);
485     }
486 
487     class CachePrefetchThread extends Thread {
488         private int mUserHandle;
489 
CachePrefetchThread(int userHandle)490         CachePrefetchThread(int userHandle) {
491             super("populate-settings-caches");
492             mUserHandle = userHandle;
493         }
494 
495         @Override
run()496         public void run() {
497             fullyPopulateCaches(mUserHandle);
498         }
499     }
500 
startAsyncCachePopulation(int userHandle)501     private void startAsyncCachePopulation(int userHandle) {
502         new CachePrefetchThread(userHandle).start();
503     }
504 
fullyPopulateCaches(final int userHandle)505     private void fullyPopulateCaches(final int userHandle) {
506         DatabaseHelper dbHelper;
507         synchronized (this) {
508             dbHelper = mOpenHelpers.get(userHandle);
509         }
510         if (dbHelper == null) {
511             // User is gone.
512             return;
513         }
514         // Only populate the globals cache once, for the owning user
515         if (userHandle == UserHandle.USER_OWNER) {
516             fullyPopulateCache(dbHelper, TABLE_GLOBAL, sGlobalCache);
517         }
518         fullyPopulateCache(dbHelper, TABLE_SECURE, sSecureCaches.get(userHandle));
519         fullyPopulateCache(dbHelper, TABLE_SYSTEM, sSystemCaches.get(userHandle));
520     }
521 
522     // Slurp all values (if sane in number & size) into cache.
fullyPopulateCache(DatabaseHelper dbHelper, String table, SettingsCache cache)523     private void fullyPopulateCache(DatabaseHelper dbHelper, String table, SettingsCache cache) {
524         SQLiteDatabase db = dbHelper.getReadableDatabase();
525         Cursor c = db.query(
526             table,
527             new String[] { Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE },
528             null, null, null, null, null,
529             "" + (MAX_CACHE_ENTRIES + 1) /* limit */);
530         try {
531             synchronized (cache) {
532                 cache.evictAll();
533                 cache.setFullyMatchesDisk(true);  // optimistic
534                 int rows = 0;
535                 while (c.moveToNext()) {
536                     rows++;
537                     String name = c.getString(0);
538                     String value = c.getString(1);
539                     cache.populate(name, value);
540                 }
541                 if (rows > MAX_CACHE_ENTRIES) {
542                     // Somewhat redundant, as removeEldestEntry() will
543                     // have already done this, but to be explicit:
544                     cache.setFullyMatchesDisk(false);
545                     Log.d(TAG, "row count exceeds max cache entries for table " + table);
546                 }
547                 if (LOCAL_LOGV) Log.d(TAG, "cache for settings table '" + table
548                         + "' rows=" + rows + "; fullycached=" + cache.fullyMatchesDisk());
549             }
550         } finally {
551             c.close();
552         }
553     }
554 
ensureAndroidIdIsSet(int userHandle)555     private boolean ensureAndroidIdIsSet(int userHandle) {
556         final Cursor c = queryForUser(Settings.Secure.CONTENT_URI,
557                 new String[] { Settings.NameValueTable.VALUE },
558                 Settings.NameValueTable.NAME + "=?",
559                 new String[] { Settings.Secure.ANDROID_ID }, null,
560                 userHandle);
561         try {
562             final String value = c.moveToNext() ? c.getString(0) : null;
563             if (value == null) {
564                 // sanity-check the user before touching the db
565                 final UserInfo user = mUserManager.getUserInfo(userHandle);
566                 if (user == null) {
567                     // can happen due to races when deleting users; treat as benign
568                     return false;
569                 }
570 
571                 final SecureRandom random = new SecureRandom();
572                 final String newAndroidIdValue = Long.toHexString(random.nextLong());
573                 final ContentValues values = new ContentValues();
574                 values.put(Settings.NameValueTable.NAME, Settings.Secure.ANDROID_ID);
575                 values.put(Settings.NameValueTable.VALUE, newAndroidIdValue);
576                 final Uri uri = insertForUser(Settings.Secure.CONTENT_URI, values, userHandle);
577                 if (uri == null) {
578                     Slog.e(TAG, "Unable to generate new ANDROID_ID for user " + userHandle);
579                     return false;
580                 }
581                 Slog.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue
582                         + "] for user " + userHandle);
583                 // Write a dropbox entry if it's a restricted profile
584                 if (user.isRestricted()) {
585                     DropBoxManager dbm = (DropBoxManager)
586                             getContext().getSystemService(Context.DROPBOX_SERVICE);
587                     if (dbm != null && dbm.isTagEnabled(DROPBOX_TAG_USERLOG)) {
588                         dbm.addText(DROPBOX_TAG_USERLOG, System.currentTimeMillis()
589                                 + ",restricted_profile_ssaid,"
590                                 + newAndroidIdValue + "\n");
591                     }
592                 }
593             }
594             return true;
595         } finally {
596             c.close();
597         }
598     }
599 
600     // Lazy-initialize the settings caches for non-primary users
getOrConstructCache(int callingUser, SparseArray<SettingsCache> which)601     private SettingsCache getOrConstructCache(int callingUser, SparseArray<SettingsCache> which) {
602         getOrEstablishDatabase(callingUser); // ignore return value; we don't need it
603         return which.get(callingUser);
604     }
605 
606     // Lazy initialize the database helper and caches for this user, if necessary
getOrEstablishDatabase(int callingUser)607     private DatabaseHelper getOrEstablishDatabase(int callingUser) {
608         if (callingUser >= Process.SYSTEM_UID) {
609             if (USER_CHECK_THROWS) {
610                 throw new IllegalArgumentException("Uid rather than user handle: " + callingUser);
611             } else {
612                 Slog.wtf(TAG, "establish db for uid rather than user: " + callingUser);
613             }
614         }
615 
616         long oldId = Binder.clearCallingIdentity();
617         try {
618             DatabaseHelper dbHelper;
619             synchronized (this) {
620                 dbHelper = mOpenHelpers.get(callingUser);
621             }
622             if (null == dbHelper) {
623                 establishDbTracking(callingUser);
624                 synchronized (this) {
625                     dbHelper = mOpenHelpers.get(callingUser);
626                 }
627             }
628             return dbHelper;
629         } finally {
630             Binder.restoreCallingIdentity(oldId);
631         }
632     }
633 
cacheForTable(final int callingUser, String tableName)634     public SettingsCache cacheForTable(final int callingUser, String tableName) {
635         if (TABLE_SYSTEM.equals(tableName)) {
636             return getOrConstructCache(callingUser, sSystemCaches);
637         }
638         if (TABLE_SECURE.equals(tableName)) {
639             return getOrConstructCache(callingUser, sSecureCaches);
640         }
641         if (TABLE_GLOBAL.equals(tableName)) {
642             return sGlobalCache;
643         }
644         return null;
645     }
646 
647     /**
648      * Used for wiping a whole cache on deletes when we're not
649      * sure what exactly was deleted or changed.
650      */
invalidateCache(final int callingUser, String tableName)651     public void invalidateCache(final int callingUser, String tableName) {
652         SettingsCache cache = cacheForTable(callingUser, tableName);
653         if (cache == null) {
654             return;
655         }
656         synchronized (cache) {
657             cache.evictAll();
658             cache.mCacheFullyMatchesDisk = false;
659         }
660     }
661 
662     /**
663      * Checks if the calling user is a managed profile of the primary user.
664      * Currently only the primary user (USER_OWNER) can have managed profiles.
665      * @param callingUser the user trying to read/write settings
666      * @return true if it is a managed profile of the primary user
667      */
isManagedProfile(int callingUser)668     private boolean isManagedProfile(int callingUser) {
669         synchronized (this) {
670             if (mManagedProfiles == null) return false;
671             for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
672                 if (mManagedProfiles.get(i).id == callingUser) {
673                     return true;
674                 }
675             }
676             return false;
677         }
678     }
679 
680     /**
681      * Fast path that avoids the use of chatty remoted Cursors.
682      */
683     @Override
call(String method, String request, Bundle args)684     public Bundle call(String method, String request, Bundle args) {
685         int callingUser = UserHandle.getCallingUserId();
686         if (args != null) {
687             int reqUser = args.getInt(Settings.CALL_METHOD_USER_KEY, callingUser);
688             if (reqUser != callingUser) {
689                 callingUser = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
690                         Binder.getCallingUid(), reqUser, false, true,
691                         "get/set setting for user", null);
692                 if (LOCAL_LOGV) Slog.v(TAG, "   access setting for user " + callingUser);
693             }
694         }
695 
696         // Note: we assume that get/put operations for moved-to-global names have already
697         // been directed to the new location on the caller side (otherwise we'd fix them
698         // up here).
699         DatabaseHelper dbHelper;
700         SettingsCache cache;
701 
702         // Get methods
703         if (Settings.CALL_METHOD_GET_SYSTEM.equals(method)) {
704             if (LOCAL_LOGV) Slog.v(TAG, "call(system:" + request + ") for " + callingUser);
705             // Check if this request should be (re)directed to the primary user's db
706             if (callingUser != UserHandle.USER_OWNER
707                     && shouldShadowParentProfile(callingUser, sSystemCloneToManagedKeys, request)) {
708                 callingUser = UserHandle.USER_OWNER;
709             }
710             dbHelper = getOrEstablishDatabase(callingUser);
711             cache = sSystemCaches.get(callingUser);
712             return lookupValue(dbHelper, TABLE_SYSTEM, cache, request);
713         }
714         if (Settings.CALL_METHOD_GET_SECURE.equals(method)) {
715             if (LOCAL_LOGV) Slog.v(TAG, "call(secure:" + request + ") for " + callingUser);
716             // Check if this is a setting to be copied from the primary user
717             if (shouldShadowParentProfile(callingUser, sSecureCloneToManagedKeys, request)) {
718                 // If the request if for location providers and there's a restriction, return none
719                 if (Secure.LOCATION_PROVIDERS_ALLOWED.equals(request)
720                         && mUserManager.hasUserRestriction(
721                                 UserManager.DISALLOW_SHARE_LOCATION, new UserHandle(callingUser))) {
722                     return sSecureCaches.get(callingUser).putIfAbsent(request, "");
723                 }
724                 callingUser = UserHandle.USER_OWNER;
725             }
726             dbHelper = getOrEstablishDatabase(callingUser);
727             cache = sSecureCaches.get(callingUser);
728             return lookupValue(dbHelper, TABLE_SECURE, cache, request);
729         }
730         if (Settings.CALL_METHOD_GET_GLOBAL.equals(method)) {
731             if (LOCAL_LOGV) Slog.v(TAG, "call(global:" + request + ") for " + callingUser);
732             // fast path: owner db & cache are immutable after onCreate() so we need not
733             // guard on the attempt to look them up
734             return lookupValue(getOrEstablishDatabase(UserHandle.USER_OWNER), TABLE_GLOBAL,
735                     sGlobalCache, request);
736         }
737 
738         // Put methods - new value is in the args bundle under the key named by
739         // the Settings.NameValueTable.VALUE static.
740         final String newValue = (args == null)
741                 ? null : args.getString(Settings.NameValueTable.VALUE);
742 
743         // Framework can't do automatic permission checking for calls, so we need
744         // to do it here.
745         if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
746                 != PackageManager.PERMISSION_GRANTED) {
747             throw new SecurityException(
748                     String.format("Permission denial: writing to settings requires %1$s",
749                                   android.Manifest.permission.WRITE_SETTINGS));
750         }
751 
752         // Also need to take care of app op.
753         if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SETTINGS, Binder.getCallingUid(),
754                 getCallingPackage()) != AppOpsManager.MODE_ALLOWED) {
755             return null;
756         }
757 
758         final ContentValues values = new ContentValues();
759         values.put(Settings.NameValueTable.NAME, request);
760         values.put(Settings.NameValueTable.VALUE, newValue);
761         if (Settings.CALL_METHOD_PUT_SYSTEM.equals(method)) {
762             if (LOCAL_LOGV) {
763                 Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for "
764                         + callingUser);
765             }
766             // Extra check for USER_OWNER to optimize for the 99%
767             if (callingUser != UserHandle.USER_OWNER && shouldShadowParentProfile(callingUser,
768                     sSystemCloneToManagedKeys, request)) {
769                 // Don't write these settings, as they are cloned from the parent profile
770                 return null;
771             }
772             insertForUser(Settings.System.CONTENT_URI, values, callingUser);
773             // Clone the settings to the managed profiles so that notifications can be sent out
774             if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null
775                     && sSystemCloneToManagedKeys.contains(request)) {
776                 final long token = Binder.clearCallingIdentity();
777                 try {
778                     for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
779                         if (LOCAL_LOGV) {
780                             Slog.v(TAG, "putting to additional user "
781                                     + mManagedProfiles.get(i).id);
782                         }
783                         insertForUser(Settings.System.CONTENT_URI, values,
784                                 mManagedProfiles.get(i).id);
785                     }
786                 } finally {
787                     Binder.restoreCallingIdentity(token);
788                 }
789             }
790         } else if (Settings.CALL_METHOD_PUT_SECURE.equals(method)) {
791             if (LOCAL_LOGV) {
792                 Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for "
793                         + callingUser);
794             }
795             // Extra check for USER_OWNER to optimize for the 99%
796             if (callingUser != UserHandle.USER_OWNER && shouldShadowParentProfile(callingUser,
797                     sSecureCloneToManagedKeys, request)) {
798                 // Don't write these settings, as they are cloned from the parent profile
799                 return null;
800             }
801             insertForUser(Settings.Secure.CONTENT_URI, values, callingUser);
802             // Clone the settings to the managed profiles so that notifications can be sent out
803             if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null
804                     && sSecureCloneToManagedKeys.contains(request)) {
805                 final long token = Binder.clearCallingIdentity();
806                 try {
807                     for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
808                         if (LOCAL_LOGV) {
809                             Slog.v(TAG, "putting to additional user "
810                                     + mManagedProfiles.get(i).id);
811                         }
812                         try {
813                             insertForUser(Settings.Secure.CONTENT_URI, values,
814                                     mManagedProfiles.get(i).id);
815                         } catch (SecurityException e) {
816                             // Temporary fix, see b/17450158
817                             Slog.w(TAG, "Cannot clone request '" + request + "' with value '"
818                                     + newValue + "' to managed profile (id "
819                                     + mManagedProfiles.get(i).id + ")", e);
820                         }
821                     }
822                 } finally {
823                     Binder.restoreCallingIdentity(token);
824                 }
825             }
826         } else if (Settings.CALL_METHOD_PUT_GLOBAL.equals(method)) {
827             if (LOCAL_LOGV) {
828                 Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for "
829                         + callingUser);
830             }
831             insertForUser(Settings.Global.CONTENT_URI, values, callingUser);
832         } else {
833             Slog.w(TAG, "call() with invalid method: " + method);
834         }
835 
836         return null;
837     }
838 
839     /**
840      * Check if the user is a managed profile and name is one of the settings to be cloned
841      * from the parent profile.
842      */
shouldShadowParentProfile(int userId, HashSet<String> keys, String name)843     private boolean shouldShadowParentProfile(int userId, HashSet<String> keys, String name) {
844         return isManagedProfile(userId) && keys.contains(name);
845     }
846 
847     // Looks up value 'key' in 'table' and returns either a single-pair Bundle,
848     // possibly with a null value, or null on failure.
lookupValue(DatabaseHelper dbHelper, String table, final SettingsCache cache, String key)849     private Bundle lookupValue(DatabaseHelper dbHelper, String table,
850             final SettingsCache cache, String key) {
851         if (cache == null) {
852            Slog.e(TAG, "cache is null for user " + UserHandle.getCallingUserId() + " : key=" + key);
853            return null;
854         }
855         synchronized (cache) {
856             Bundle value = cache.get(key);
857             if (value != null) {
858                 if (value != TOO_LARGE_TO_CACHE_MARKER) {
859                     return value;
860                 }
861                 // else we fall through and read the value from disk
862             } else if (cache.fullyMatchesDisk()) {
863                 // Fast path (very common).  Don't even try touch disk
864                 // if we know we've slurped it all in.  Trying to
865                 // touch the disk would mean waiting for yaffs2 to
866                 // give us access, which could takes hundreds of
867                 // milliseconds.  And we're very likely being called
868                 // from somebody's UI thread...
869                 return NULL_SETTING;
870             }
871         }
872 
873         SQLiteDatabase db = dbHelper.getReadableDatabase();
874         Cursor cursor = null;
875         try {
876             cursor = db.query(table, COLUMN_VALUE, "name=?", new String[]{key},
877                               null, null, null, null);
878             if (cursor != null && cursor.getCount() == 1) {
879                 cursor.moveToFirst();
880                 return cache.putIfAbsent(key, cursor.getString(0));
881             }
882         } catch (SQLiteException e) {
883             Log.w(TAG, "settings lookup error", e);
884             return null;
885         } finally {
886             if (cursor != null) cursor.close();
887         }
888         cache.putIfAbsent(key, null);
889         return NULL_SETTING;
890     }
891 
892     @Override
query(Uri url, String[] select, String where, String[] whereArgs, String sort)893     public Cursor query(Uri url, String[] select, String where, String[] whereArgs, String sort) {
894         return queryForUser(url, select, where, whereArgs, sort, UserHandle.getCallingUserId());
895     }
896 
queryForUser(Uri url, String[] select, String where, String[] whereArgs, String sort, int forUser)897     private Cursor queryForUser(Uri url, String[] select, String where, String[] whereArgs,
898             String sort, int forUser) {
899         if (LOCAL_LOGV) Slog.v(TAG, "query(" + url + ") for user " + forUser);
900         SqlArguments args = new SqlArguments(url, where, whereArgs);
901         DatabaseHelper dbH;
902         dbH = getOrEstablishDatabase(
903                 TABLE_GLOBAL.equals(args.table) ? UserHandle.USER_OWNER : forUser);
904         SQLiteDatabase db = dbH.getReadableDatabase();
905 
906         // The favorites table was moved from this provider to a provider inside Home
907         // Home still need to query this table to upgrade from pre-cupcake builds
908         // However, a cupcake+ build with no data does not contain this table which will
909         // cause an exception in the SQL stack. The following line is a special case to
910         // let the caller of the query have a chance to recover and avoid the exception
911         if (TABLE_FAVORITES.equals(args.table)) {
912             return null;
913         } else if (TABLE_OLD_FAVORITES.equals(args.table)) {
914             args.table = TABLE_FAVORITES;
915             Cursor cursor = db.rawQuery("PRAGMA table_info(favorites);", null);
916             if (cursor != null) {
917                 boolean exists = cursor.getCount() > 0;
918                 cursor.close();
919                 if (!exists) return null;
920             } else {
921                 return null;
922             }
923         }
924 
925         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
926         qb.setTables(args.table);
927 
928         Cursor ret = qb.query(db, select, args.where, args.args, null, null, sort);
929         // the default Cursor interface does not support per-user observation
930         try {
931             AbstractCursor c = (AbstractCursor) ret;
932             c.setNotificationUri(getContext().getContentResolver(), url, forUser);
933         } catch (ClassCastException e) {
934             // details of the concrete Cursor implementation have changed and this code has
935             // not been updated to match -- complain and fail hard.
936             Log.wtf(TAG, "Incompatible cursor derivation!");
937             throw e;
938         }
939         return ret;
940     }
941 
942     @Override
getType(Uri url)943     public String getType(Uri url) {
944         // If SqlArguments supplies a where clause, then it must be an item
945         // (because we aren't supplying our own where clause).
946         SqlArguments args = new SqlArguments(url, null, null);
947         if (TextUtils.isEmpty(args.where)) {
948             return "vnd.android.cursor.dir/" + args.table;
949         } else {
950             return "vnd.android.cursor.item/" + args.table;
951         }
952     }
953 
954     @Override
bulkInsert(Uri uri, ContentValues[] values)955     public int bulkInsert(Uri uri, ContentValues[] values) {
956         final int callingUser = UserHandle.getCallingUserId();
957         if (LOCAL_LOGV) Slog.v(TAG, "bulkInsert() for user " + callingUser);
958         SqlArguments args = new SqlArguments(uri);
959         if (TABLE_FAVORITES.equals(args.table)) {
960             return 0;
961         }
962         checkWritePermissions(args);
963         SettingsCache cache = cacheForTable(callingUser, args.table);
964 
965         final AtomicInteger mutationCount;
966         synchronized (this) {
967             mutationCount = sKnownMutationsInFlight.get(callingUser);
968         }
969         if (mutationCount != null) {
970             mutationCount.incrementAndGet();
971         }
972         DatabaseHelper dbH = getOrEstablishDatabase(
973                 TABLE_GLOBAL.equals(args.table) ? UserHandle.USER_OWNER : callingUser);
974         SQLiteDatabase db = dbH.getWritableDatabase();
975         db.beginTransaction();
976         try {
977             int numValues = values.length;
978             for (int i = 0; i < numValues; i++) {
979                 checkUserRestrictions(values[i].getAsString(Settings.Secure.NAME), callingUser);
980                 if (db.insert(args.table, null, values[i]) < 0) return 0;
981                 SettingsCache.populate(cache, values[i]);
982                 if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + values[i]);
983             }
984             db.setTransactionSuccessful();
985         } finally {
986             db.endTransaction();
987             if (mutationCount != null) {
988                 mutationCount.decrementAndGet();
989             }
990         }
991 
992         sendNotify(uri, callingUser);
993         return values.length;
994     }
995 
996     /*
997      * Used to parse changes to the value of Settings.Secure.LOCATION_PROVIDERS_ALLOWED.
998      * This setting contains a list of the currently enabled location providers.
999      * But helper functions in android.providers.Settings can enable or disable
1000      * a single provider by using a "+" or "-" prefix before the provider name.
1001      *
1002      * @returns whether the database needs to be updated or not, also modifying
1003      *     'initialValues' if needed.
1004      */
parseProviderList(Uri url, ContentValues initialValues, int desiredUser)1005     private boolean parseProviderList(Uri url, ContentValues initialValues, int desiredUser) {
1006         String value = initialValues.getAsString(Settings.Secure.VALUE);
1007         String newProviders = null;
1008         if (value != null && value.length() > 1) {
1009             char prefix = value.charAt(0);
1010             if (prefix == '+' || prefix == '-') {
1011                 // skip prefix
1012                 value = value.substring(1);
1013 
1014                 // read list of enabled providers into "providers"
1015                 String providers = "";
1016                 String[] columns = {Settings.Secure.VALUE};
1017                 String where = Settings.Secure.NAME + "=\'" + Settings.Secure.LOCATION_PROVIDERS_ALLOWED + "\'";
1018                 Cursor cursor = queryForUser(url, columns, where, null, null, desiredUser);
1019                 if (cursor != null && cursor.getCount() == 1) {
1020                     try {
1021                         cursor.moveToFirst();
1022                         providers = cursor.getString(0);
1023                     } finally {
1024                         cursor.close();
1025                     }
1026                 }
1027 
1028                 int index = providers.indexOf(value);
1029                 int end = index + value.length();
1030                 // check for commas to avoid matching on partial string
1031                 if (index > 0 && providers.charAt(index - 1) != ',') index = -1;
1032                 if (end < providers.length() && providers.charAt(end) != ',') index = -1;
1033 
1034                 if (prefix == '+' && index < 0) {
1035                     // append the provider to the list if not present
1036                     if (providers.length() == 0) {
1037                         newProviders = value;
1038                     } else {
1039                         newProviders = providers + ',' + value;
1040                     }
1041                 } else if (prefix == '-' && index >= 0) {
1042                     // remove the provider from the list if present
1043                     // remove leading or trailing comma
1044                     if (index > 0) {
1045                         index--;
1046                     } else if (end < providers.length()) {
1047                         end++;
1048                     }
1049 
1050                     newProviders = providers.substring(0, index);
1051                     if (end < providers.length()) {
1052                         newProviders += providers.substring(end);
1053                     }
1054                 } else {
1055                     // nothing changed, so no need to update the database
1056                     return false;
1057                 }
1058 
1059                 if (newProviders != null) {
1060                     initialValues.put(Settings.Secure.VALUE, newProviders);
1061                 }
1062             }
1063         }
1064 
1065         return true;
1066     }
1067 
1068     @Override
insert(Uri url, ContentValues initialValues)1069     public Uri insert(Uri url, ContentValues initialValues) {
1070         return insertForUser(url, initialValues, UserHandle.getCallingUserId());
1071     }
1072 
1073     // Settings.put*ForUser() always winds up here, so this is where we apply
1074     // policy around permission to write settings for other users.
insertForUser(Uri url, ContentValues initialValues, int desiredUserHandle)1075     private Uri insertForUser(Uri url, ContentValues initialValues, int desiredUserHandle) {
1076         final int callingUser = UserHandle.getCallingUserId();
1077         if (callingUser != desiredUserHandle) {
1078             getContext().enforceCallingOrSelfPermission(
1079                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
1080                     "Not permitted to access settings for other users");
1081         }
1082 
1083         if (LOCAL_LOGV) Slog.v(TAG, "insert(" + url + ") for user " + desiredUserHandle
1084                 + " by " + callingUser);
1085 
1086         SqlArguments args = new SqlArguments(url);
1087         if (TABLE_FAVORITES.equals(args.table)) {
1088             return null;
1089         }
1090 
1091         // Special case LOCATION_PROVIDERS_ALLOWED.
1092         // Support enabling/disabling a single provider (using "+" or "-" prefix)
1093         String name = initialValues.getAsString(Settings.Secure.NAME);
1094         if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
1095             if (!parseProviderList(url, initialValues, desiredUserHandle)) return null;
1096         }
1097 
1098         // If this is an insert() of a key that has been migrated to the global store,
1099         // redirect the operation to that store
1100         if (name != null) {
1101             if (sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name)) {
1102                 if (!TABLE_GLOBAL.equals(args.table)) {
1103                     if (LOCAL_LOGV) Slog.i(TAG, "Rewrite of insert() of now-global key " + name);
1104                 }
1105                 args.table = TABLE_GLOBAL;  // next condition will rewrite the user handle
1106             }
1107         }
1108 
1109         // Check write permissions only after determining which table the insert will touch
1110         checkWritePermissions(args);
1111 
1112         checkUserRestrictions(name, desiredUserHandle);
1113 
1114         // The global table is stored under the owner, always
1115         if (TABLE_GLOBAL.equals(args.table)) {
1116             desiredUserHandle = UserHandle.USER_OWNER;
1117         }
1118 
1119         SettingsCache cache = cacheForTable(desiredUserHandle, args.table);
1120         String value = initialValues.getAsString(Settings.NameValueTable.VALUE);
1121         if (SettingsCache.isRedundantSetValue(cache, name, value)) {
1122             return Uri.withAppendedPath(url, name);
1123         }
1124 
1125         final AtomicInteger mutationCount;
1126         synchronized (this) {
1127             mutationCount = sKnownMutationsInFlight.get(callingUser);
1128         }
1129         if (mutationCount != null) {
1130             mutationCount.incrementAndGet();
1131         }
1132         DatabaseHelper dbH = getOrEstablishDatabase(desiredUserHandle);
1133         SQLiteDatabase db = dbH.getWritableDatabase();
1134         final long rowId = db.insert(args.table, null, initialValues);
1135         if (mutationCount != null) {
1136             mutationCount.decrementAndGet();
1137         }
1138         if (rowId <= 0) return null;
1139 
1140         SettingsCache.populate(cache, initialValues);  // before we notify
1141 
1142         if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + initialValues
1143                 + " for user " + desiredUserHandle);
1144         // Note that we use the original url here, not the potentially-rewritten table name
1145         url = getUriFor(url, initialValues, rowId);
1146         sendNotify(url, desiredUserHandle);
1147         return url;
1148     }
1149 
1150     @Override
delete(Uri url, String where, String[] whereArgs)1151     public int delete(Uri url, String where, String[] whereArgs) {
1152         int callingUser = UserHandle.getCallingUserId();
1153         if (LOCAL_LOGV) Slog.v(TAG, "delete() for user " + callingUser);
1154         SqlArguments args = new SqlArguments(url, where, whereArgs);
1155         if (TABLE_FAVORITES.equals(args.table)) {
1156             return 0;
1157         } else if (TABLE_OLD_FAVORITES.equals(args.table)) {
1158             args.table = TABLE_FAVORITES;
1159         } else if (TABLE_GLOBAL.equals(args.table)) {
1160             callingUser = UserHandle.USER_OWNER;
1161         }
1162         checkWritePermissions(args);
1163 
1164         final AtomicInteger mutationCount;
1165         synchronized (this) {
1166             mutationCount = sKnownMutationsInFlight.get(callingUser);
1167         }
1168         if (mutationCount != null) {
1169             mutationCount.incrementAndGet();
1170         }
1171         DatabaseHelper dbH = getOrEstablishDatabase(callingUser);
1172         SQLiteDatabase db = dbH.getWritableDatabase();
1173         int count = db.delete(args.table, args.where, args.args);
1174         if (mutationCount != null) {
1175             mutationCount.decrementAndGet();
1176         }
1177         if (count > 0) {
1178             invalidateCache(callingUser, args.table);  // before we notify
1179             sendNotify(url, callingUser);
1180         }
1181         startAsyncCachePopulation(callingUser);
1182         if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) deleted");
1183         return count;
1184     }
1185 
1186     @Override
update(Uri url, ContentValues initialValues, String where, String[] whereArgs)1187     public int update(Uri url, ContentValues initialValues, String where, String[] whereArgs) {
1188         // NOTE: update() is never called by the front-end Settings API, and updates that
1189         // wind up affecting rows in Secure that are globally shared will not have the
1190         // intended effect (the update will be invisible to the rest of the system).
1191         // This should have no practical effect, since writes to the Secure db can only
1192         // be done by system code, and that code should be using the correct API up front.
1193         int callingUser = UserHandle.getCallingUserId();
1194         if (LOCAL_LOGV) Slog.v(TAG, "update() for user " + callingUser);
1195         SqlArguments args = new SqlArguments(url, where, whereArgs);
1196         if (TABLE_FAVORITES.equals(args.table)) {
1197             return 0;
1198         } else if (TABLE_GLOBAL.equals(args.table)) {
1199             callingUser = UserHandle.USER_OWNER;
1200         }
1201         checkWritePermissions(args);
1202         checkUserRestrictions(initialValues.getAsString(Settings.Secure.NAME), callingUser);
1203 
1204         final AtomicInteger mutationCount;
1205         synchronized (this) {
1206             mutationCount = sKnownMutationsInFlight.get(callingUser);
1207         }
1208         if (mutationCount != null) {
1209             mutationCount.incrementAndGet();
1210         }
1211         DatabaseHelper dbH = getOrEstablishDatabase(callingUser);
1212         SQLiteDatabase db = dbH.getWritableDatabase();
1213         int count = db.update(args.table, initialValues, args.where, args.args);
1214         if (mutationCount != null) {
1215             mutationCount.decrementAndGet();
1216         }
1217         if (count > 0) {
1218             invalidateCache(callingUser, args.table);  // before we notify
1219             sendNotify(url, callingUser);
1220         }
1221         startAsyncCachePopulation(callingUser);
1222         if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) <- " + initialValues);
1223         return count;
1224     }
1225 
1226     @Override
openFile(Uri uri, String mode)1227     public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
1228         throw new FileNotFoundException("Direct file access no longer supported; "
1229                 + "ringtone playback is available through android.media.Ringtone");
1230     }
1231 
1232     /**
1233      * In-memory LRU Cache of system and secure settings, along with
1234      * associated helper functions to keep cache coherent with the
1235      * database.
1236      */
1237     private static final class SettingsCache extends LruCache<String, Bundle> {
1238 
1239         private final String mCacheName;
1240         private boolean mCacheFullyMatchesDisk = false;  // has the whole database slurped.
1241 
SettingsCache(String name)1242         public SettingsCache(String name) {
1243             super(MAX_CACHE_ENTRIES);
1244             mCacheName = name;
1245         }
1246 
1247         /**
1248          * Is the whole database table slurped into this cache?
1249          */
fullyMatchesDisk()1250         public boolean fullyMatchesDisk() {
1251             synchronized (this) {
1252                 return mCacheFullyMatchesDisk;
1253             }
1254         }
1255 
setFullyMatchesDisk(boolean value)1256         public void setFullyMatchesDisk(boolean value) {
1257             synchronized (this) {
1258                 mCacheFullyMatchesDisk = value;
1259             }
1260         }
1261 
1262         @Override
entryRemoved(boolean evicted, String key, Bundle oldValue, Bundle newValue)1263         protected void entryRemoved(boolean evicted, String key, Bundle oldValue, Bundle newValue) {
1264             if (evicted) {
1265                 mCacheFullyMatchesDisk = false;
1266             }
1267         }
1268 
1269         /**
1270          * Atomic cache population, conditional on size of value and if
1271          * we lost a race.
1272          *
1273          * @returns a Bundle to send back to the client from call(), even
1274          *     if we lost the race.
1275          */
putIfAbsent(String key, String value)1276         public Bundle putIfAbsent(String key, String value) {
1277             Bundle bundle = (value == null) ? NULL_SETTING : Bundle.forPair("value", value);
1278             if (value == null || value.length() <= MAX_CACHE_ENTRY_SIZE) {
1279                 synchronized (this) {
1280                     if (get(key) == null) {
1281                         put(key, bundle);
1282                     }
1283                 }
1284             }
1285             return bundle;
1286         }
1287 
1288         /**
1289          * Populates a key in a given (possibly-null) cache.
1290          */
populate(SettingsCache cache, ContentValues contentValues)1291         public static void populate(SettingsCache cache, ContentValues contentValues) {
1292             if (cache == null) {
1293                 return;
1294             }
1295             String name = contentValues.getAsString(Settings.NameValueTable.NAME);
1296             if (name == null) {
1297                 Log.w(TAG, "null name populating settings cache.");
1298                 return;
1299             }
1300             String value = contentValues.getAsString(Settings.NameValueTable.VALUE);
1301             cache.populate(name, value);
1302         }
1303 
populate(String name, String value)1304         public void populate(String name, String value) {
1305             synchronized (this) {
1306                 if (value == null || value.length() <= MAX_CACHE_ENTRY_SIZE) {
1307                     put(name, Bundle.forPair(Settings.NameValueTable.VALUE, value));
1308                 } else {
1309                     put(name, TOO_LARGE_TO_CACHE_MARKER);
1310                 }
1311             }
1312         }
1313 
1314         /**
1315          * For suppressing duplicate/redundant settings inserts early,
1316          * checking our cache first (but without faulting it in),
1317          * before going to sqlite with the mutation.
1318          */
isRedundantSetValue(SettingsCache cache, String name, String value)1319         public static boolean isRedundantSetValue(SettingsCache cache, String name, String value) {
1320             if (cache == null) return false;
1321             synchronized (cache) {
1322                 Bundle bundle = cache.get(name);
1323                 if (bundle == null) return false;
1324                 String oldValue = bundle.getPairValue();
1325                 if (oldValue == null && value == null) return true;
1326                 if ((oldValue == null) != (value == null)) return false;
1327                 return oldValue.equals(value);
1328             }
1329         }
1330     }
1331 }
1332