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
17package com.android.providers.settings;
18
19import java.io.FileNotFoundException;
20import java.security.SecureRandom;
21import java.util.HashMap;
22import java.util.HashSet;
23import java.util.List;
24import java.util.Map;
25import java.util.concurrent.atomic.AtomicBoolean;
26import java.util.concurrent.atomic.AtomicInteger;
27
28import android.app.ActivityManager;
29import android.app.AppOpsManager;
30import android.app.backup.BackupManager;
31import android.content.BroadcastReceiver;
32import android.content.ContentProvider;
33import android.content.ContentUris;
34import android.content.ContentValues;
35import android.content.Context;
36import android.content.Intent;
37import android.content.IntentFilter;
38import android.content.pm.PackageManager;
39import android.content.pm.UserInfo;
40import android.database.AbstractCursor;
41import android.database.Cursor;
42import android.database.sqlite.SQLiteDatabase;
43import android.database.sqlite.SQLiteException;
44import android.database.sqlite.SQLiteQueryBuilder;
45import android.net.Uri;
46import android.os.Binder;
47import android.os.Bundle;
48import android.os.DropBoxManager;
49import android.os.FileObserver;
50import android.os.ParcelFileDescriptor;
51import android.os.Process;
52import android.os.SystemProperties;
53import android.os.UserHandle;
54import android.os.UserManager;
55import android.provider.Settings;
56import android.provider.Settings.Secure;
57import android.text.TextUtils;
58import android.util.Log;
59import android.util.LruCache;
60import android.util.Slog;
61import android.util.SparseArray;
62
63public 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>();
140        sRestrictedKeys.put(Settings.Secure.LOCATION_MODE, UserManager.DISALLOW_SHARE_LOCATION);
141        sRestrictedKeys.put(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
142                UserManager.DISALLOW_SHARE_LOCATION);
143        sRestrictedKeys.put(Settings.Secure.INSTALL_NON_MARKET_APPS,
144                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
145        sRestrictedKeys.put(Settings.Global.ADB_ENABLED, UserManager.DISALLOW_DEBUGGING_FEATURES);
146        sRestrictedKeys.put(Settings.Global.PACKAGE_VERIFIER_ENABLE,
147                UserManager.ENSURE_VERIFY_APPS);
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
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. */
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). */
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     */
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     */
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     */
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
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
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
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
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
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     */
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
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
490        CachePrefetchThread(int userHandle) {
491            super("populate-settings-caches");
492            mUserHandle = userHandle;
493        }
494
495        @Override
496        public void run() {
497            fullyPopulateCaches(mUserHandle);
498        }
499    }
500
501    private void startAsyncCachePopulation(int userHandle) {
502        new CachePrefetchThread(userHandle).start();
503    }
504
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.
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
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
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
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
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     */
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     */
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
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     */
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.
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
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
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
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
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     */
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
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.
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
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
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
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
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         */
1250        public boolean fullyMatchesDisk() {
1251            synchronized (this) {
1252                return mCacheFullyMatchesDisk;
1253            }
1254        }
1255
1256        public void setFullyMatchesDisk(boolean value) {
1257            synchronized (this) {
1258                mCacheFullyMatchesDisk = value;
1259            }
1260        }
1261
1262        @Override
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         */
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         */
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
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         */
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