Home | History | Annotate | Download | only in settings
      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>();
    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