Rewrite of the settings provider.

This change modifies how global, secure, and system settings are
managed. In particular, we are moving away from the database to
an in-memory model where the settings are persisted asynchronously
to XML.

This simplifies evolution and improves performance, for example,
changing a setting is down from around 400 ms to 10 ms as we do not
hit the disk. The trade off is that we may lose data if the system
dies before persisting the change.

In practice this is not a problem because 1) this is very rare;
2) apps changing a setting use the setting itself to know if it
changed, so next time the app runs (after a reboot that lost data)
the app will be oblivious that data was lost.

When persisting the settings we delay the write a bit to batch
multiple changes. If a change occurs we reschedule the write
but when a maximal delay occurs after the first non-persisted
change we write to disk no matter what. This prevents a malicious
app poking the settings all the time to prevent them being persisted.

The settings are persisted in separate XML files for each type of
setting per user. Specifically, they are in the user's system
directory and the files are named: settings_type_of_settings.xml.

Data migration is performed after the data base is upgraded to its
last version after which the global, system, and secure tables are
dropped.

The global, secure, and system settings now have the same version
and are upgraded as a whole per user to allow migration of settings
between these them. The upgrade steps should be added to the
SettingsProvider.UpgradeController and not in the DatabaseHelper.

Setting states are mapped to an integer key derived from the user
id and the setting type. Therefore, all setting states are in
a lookup table which makes all opertions very fast.

The code is a complete rewrite aiming for improved clarity and
increased maintainability as opposed to using minor optimizations.
Now setting and getting the changed setting takes around 10 ms. We
can optimize later if needed.

Now the code path through the call API and the one through the
content provider APIs end up being the same which fixes bugs where
some enterprise cases were not implemented in the content provider
code path.

Note that we are keeping the call code path as it is a bit faster
than the provider APIs with about 2 ms for setting and getting
a setting. The front-end settings APIs use the call method.

Further, we are restricting apps writing to the system settings.
If the app is targeting API higher than Lollipop MR1 we do not
let them have their settings in the system ones. Otherwise, we
warn that this will become an error. System apps like GMS core
can change anything like the system or shell or root.

Since old apps can add their settings, this can increase the
system memory footprint with no limit. Therefore, we limit the
amount of settings data an app can write to the system settings
before starting to reject new data.

Another problem with the system settings was that an app with a
permission to write there can put invalid values for the settings.
We now have validators for these settings that ensure only valid
values are accepted.

Since apps can put their settings in the system table, when the
app is uninstalled this data is stale in the sytem table without
ever being used. Now we keep the package that last changed the
setting and when the package is removed all settings it touched
that are not in the ones defined in the APIs are dropped.

Keeping in memory settings means that we cannot handle arbitrary
SQL operations, rather the supported operations are on a single
setting by name and all settings (querying). This should not be
a problem in practice but we have to verify it. For that reason,
we log unsupported SQL operations to the event log to do some
crunching and see what if any cases we should additionally support.

There are also tests for the settings provider in this change.

Change-Id: I941dc6e567588d9812905b147dbe1a3191c8dd68
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 6828301..ff2c004 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -16,1211 +16,461 @@
 
 package com.android.providers.settings;
 
-import java.io.FileNotFoundException;
-import java.security.SecureRandom;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.backup.BackupManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentProvider;
-import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.database.AbstractCursor;
 import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteQueryBuilder;
+import android.hardware.camera2.utils.ArrayUtils;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.DropBoxManager;
-import android.os.FileObserver;
+import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.provider.Settings.Secure;
 import android.text.TextUtils;
-import android.util.Log;
-import android.util.LruCache;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.BackgroundThread;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
 
+import com.android.providers.settings.SettingsState.Setting;
+
+/**
+ * <p>
+ * This class is a content provider that publishes the system settings.
+ * It can be accessed via the content provider APIs or via custom call
+ * commands. The latter is a bit faster and is the preferred way to access
+ * the platform settings.
+ * </p>
+ * <p>
+ * There are three settings types, global (with signature level protection
+ * and shared across users), secure (with signature permission level
+ * protection and per user), and system (with dangerous permission level
+ * protection and per user). Global settings are stored under the device owner.
+ * Each of these settings is represented by a {@link
+ * com.android.providers.settings.SettingsState} object mapped to an integer
+ * key derived from the setting type in the most significant bits and user
+ * id in the least significant bits. Settings are synchronously loaded on
+ * instantiation of a SettingsState and asynchronously persisted on mutation.
+ * Settings are stored in the user specific system directory.
+ * </p>
+ * <p>
+ * Apps targeting APIs Lollipop MR1 and lower can add custom settings entries
+ * and get a warning. Targeting higher API version prohibits this as the
+ * system settings are not a place for apps to save their state. When a package
+ * is removed the settings it added are deleted. Apps cannot delete system
+ * settings added by the platform. System settings values are validated to
+ * ensure the clients do not put bad values. Global and secure settings are
+ * changed only by trusted parties, therefore no validation is performed. Also
+ * there is a limit on the amount of app specific settings that can be added
+ * to prevent unlimited growth of the system process memory footprint.
+ * </p>
+ */
+@SuppressWarnings("deprecation")
 public class SettingsProvider extends ContentProvider {
-    private static final String TAG = "SettingsProvider";
-    private static final boolean LOCAL_LOGV = false;
+    private static final boolean DEBUG = false;
 
-    private static final boolean USER_CHECK_THROWS = true;
+    private static final boolean DROP_DATABASE_ON_MIGRATION = !Build.IS_DEBUGGABLE;
+
+    private static final String LOG_TAG = "SettingsProvider";
 
     private static final String TABLE_SYSTEM = "system";
     private static final String TABLE_SECURE = "secure";
     private static final String TABLE_GLOBAL = "global";
+
+    // Old tables no longer exist.
     private static final String TABLE_FAVORITES = "favorites";
     private static final String TABLE_OLD_FAVORITES = "old_favorites";
+    private static final String TABLE_BLUETOOTH_DEVICES = "bluetooth_devices";
+    private static final String TABLE_BOOKMARKS = "bookmarks";
+    private static final String TABLE_ANDROID_METADATA = "android_metadata";
 
-    private static final String[] COLUMN_VALUE = new String[] { "value" };
-
-    // Caches for each user's settings, access-ordered for acting as LRU.
-    // Guarded by themselves.
-    private static final int MAX_CACHE_ENTRIES = 200;
-    private static final SparseArray<SettingsCache> sSystemCaches
-            = new SparseArray<SettingsCache>();
-    private static final SparseArray<SettingsCache> sSecureCaches
-            = new SparseArray<SettingsCache>();
-    private static final SettingsCache sGlobalCache = new SettingsCache(TABLE_GLOBAL);
-
-    // The count of how many known (handled by SettingsProvider)
-    // database mutations are currently being handled for this user.
-    // Used by file observers to not reload the database when it's ourselves
-    // modifying it.
-    private static final SparseArray<AtomicInteger> sKnownMutationsInFlight
-            = new SparseArray<AtomicInteger>();
-
-    // Each defined user has their own settings
-    protected final SparseArray<DatabaseHelper> mOpenHelpers = new SparseArray<DatabaseHelper>();
-
-    // Keep the list of managed profiles synced here
-    private List<UserInfo> mManagedProfiles = null;
-
-    // Over this size we don't reject loading or saving settings but
-    // we do consider them broken/malicious and don't keep them in
-    // memory at least:
-    private static final int MAX_CACHE_ENTRY_SIZE = 500;
-
-    private static final Bundle NULL_SETTING = Bundle.forPair("value", null);
-
-    // Used as a sentinel value in an instance equality test when we
-    // want to cache the existence of a key, but not store its value.
-    private static final Bundle TOO_LARGE_TO_CACHE_MARKER = Bundle.forPair("_dummy", null);
-
-    private UserManager mUserManager;
-    private BackupManager mBackupManager;
-
-    /**
-     * Settings which need to be treated as global/shared in multi-user environments.
-     */
-    static final HashSet<String> sSecureGlobalKeys;
-    static final HashSet<String> sSystemGlobalKeys;
-
-    // Settings that cannot be modified if associated user restrictions are enabled.
-    static final Map<String, String> sRestrictedKeys;
-
-    private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
-
-    static final HashSet<String> sSecureCloneToManagedKeys;
-    static final HashSet<String> sSystemCloneToManagedKeys;
-
+    // The set of removed legacy tables.
+    private static final Set<String> REMOVED_LEGACY_TABLES = new ArraySet<>();
     static {
-        // Keys (name column) from the 'secure' table that are now in the owner user's 'global'
-        // table, shared across all users
-        // These must match Settings.Secure.MOVED_TO_GLOBAL
-        sSecureGlobalKeys = new HashSet<String>();
-        Settings.Secure.getMovedKeys(sSecureGlobalKeys);
+        REMOVED_LEGACY_TABLES.add(TABLE_FAVORITES);
+        REMOVED_LEGACY_TABLES.add(TABLE_OLD_FAVORITES);
+        REMOVED_LEGACY_TABLES.add(TABLE_BLUETOOTH_DEVICES);
+        REMOVED_LEGACY_TABLES.add(TABLE_BOOKMARKS);
+        REMOVED_LEGACY_TABLES.add(TABLE_ANDROID_METADATA);
+    }
 
-        // Keys from the 'system' table now moved to 'global'
-        // These must match Settings.System.MOVED_TO_GLOBAL
-        sSystemGlobalKeys = new HashSet<String>();
-        Settings.System.getNonLegacyMovedKeys(sSystemGlobalKeys);
+    private static final int MUTATION_OPERATION_INSERT = 1;
+    private static final int MUTATION_OPERATION_DELETE = 2;
+    private static final int MUTATION_OPERATION_UPDATE = 3;
 
-        sRestrictedKeys = new HashMap<String, String>();
-        sRestrictedKeys.put(Settings.Secure.LOCATION_MODE, UserManager.DISALLOW_SHARE_LOCATION);
-        sRestrictedKeys.put(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+    private static final String[] ALL_COLUMNS = new String[] {
+            Settings.NameValueTable._ID,
+            Settings.NameValueTable.NAME,
+            Settings.NameValueTable.VALUE
+    };
+
+    private static final Bundle NULL_SETTING = Bundle.forPair(Settings.NameValueTable.VALUE, null);
+
+    // Per user settings that cannot be modified if associated user restrictions are enabled.
+    private static final Map<String, String> sSettingToUserRestrictionMap = new ArrayMap<>();
+    static {
+        sSettingToUserRestrictionMap.put(Settings.Secure.LOCATION_MODE,
                 UserManager.DISALLOW_SHARE_LOCATION);
-        sRestrictedKeys.put(Settings.Secure.INSTALL_NON_MARKET_APPS,
+        sSettingToUserRestrictionMap.put(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                UserManager.DISALLOW_SHARE_LOCATION);
+        sSettingToUserRestrictionMap.put(Settings.Secure.INSTALL_NON_MARKET_APPS,
                 UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
-        sRestrictedKeys.put(Settings.Global.ADB_ENABLED, UserManager.DISALLOW_DEBUGGING_FEATURES);
-        sRestrictedKeys.put(Settings.Global.PACKAGE_VERIFIER_ENABLE,
+        sSettingToUserRestrictionMap.put(Settings.Global.ADB_ENABLED,
+                UserManager.DISALLOW_DEBUGGING_FEATURES);
+        sSettingToUserRestrictionMap.put(Settings.Global.PACKAGE_VERIFIER_ENABLE,
                 UserManager.ENSURE_VERIFY_APPS);
-        sRestrictedKeys.put(Settings.Global.PREFERRED_NETWORK_MODE,
+        sSettingToUserRestrictionMap.put(Settings.Global.PREFERRED_NETWORK_MODE,
                 UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
-
-        sSecureCloneToManagedKeys = new HashSet<String>();
-        for (int i = 0; i < Settings.Secure.CLONE_TO_MANAGED_PROFILE.length; i++) {
-            sSecureCloneToManagedKeys.add(Settings.Secure.CLONE_TO_MANAGED_PROFILE[i]);
-        }
-        sSystemCloneToManagedKeys = new HashSet<String>();
-        for (int i = 0; i < Settings.System.CLONE_TO_MANAGED_PROFILE.length; i++) {
-            sSystemCloneToManagedKeys.add(Settings.System.CLONE_TO_MANAGED_PROFILE[i]);
-        }
     }
 
-    private boolean settingMovedToGlobal(final String name) {
-        return sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name);
+    // Per user secure settings that moved to the for all users global settings.
+    static final Set<String> sSecureMovedToGlobalSettings = new ArraySet<>();
+    static {
+        Settings.Secure.getMovedToGlobalSettings(sSecureMovedToGlobalSettings);
     }
 
-    /**
-     * Decode a content URL into the table, projection, and arguments
-     * used to access the corresponding database rows.
-     */
-    private static class SqlArguments {
-        public String table;
-        public final String where;
-        public final String[] args;
-
-        /** Operate on existing rows. */
-        SqlArguments(Uri url, String where, String[] args) {
-            if (url.getPathSegments().size() == 1) {
-                // of the form content://settings/secure, arbitrary where clause
-                this.table = url.getPathSegments().get(0);
-                if (!DatabaseHelper.isValidTable(this.table)) {
-                    throw new IllegalArgumentException("Bad root path: " + this.table);
-                }
-                this.where = where;
-                this.args = args;
-            } else if (url.getPathSegments().size() != 2) {
-                throw new IllegalArgumentException("Invalid URI: " + url);
-            } else if (!TextUtils.isEmpty(where)) {
-                throw new UnsupportedOperationException("WHERE clause not supported: " + url);
-            } else {
-                // of the form content://settings/secure/element_name, no where clause
-                this.table = url.getPathSegments().get(0);
-                if (!DatabaseHelper.isValidTable(this.table)) {
-                    throw new IllegalArgumentException("Bad root path: " + this.table);
-                }
-                if (TABLE_SYSTEM.equals(this.table) || TABLE_SECURE.equals(this.table) ||
-                    TABLE_GLOBAL.equals(this.table)) {
-                    this.where = Settings.NameValueTable.NAME + "=?";
-                    final String name = url.getPathSegments().get(1);
-                    this.args = new String[] { name };
-                    // Rewrite the table for known-migrated names
-                    if (TABLE_SYSTEM.equals(this.table) || TABLE_SECURE.equals(this.table)) {
-                        if (sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name)) {
-                            this.table = TABLE_GLOBAL;
-                        }
-                    }
-                } else {
-                    // of the form content://bookmarks/19
-                    this.where = "_id=" + ContentUris.parseId(url);
-                    this.args = null;
-                }
-            }
-        }
-
-        /** Insert new rows (no where clause allowed). */
-        SqlArguments(Uri url) {
-            if (url.getPathSegments().size() == 1) {
-                this.table = url.getPathSegments().get(0);
-                if (!DatabaseHelper.isValidTable(this.table)) {
-                    throw new IllegalArgumentException("Bad root path: " + this.table);
-                }
-                this.where = null;
-                this.args = null;
-            } else {
-                throw new IllegalArgumentException("Invalid URI: " + url);
-            }
-        }
+    // Per user system settings that moved to the for all users global settings.
+    static final Set<String> sSystemMovedToGlobalSettings = new ArraySet<>();
+    static {
+        Settings.System.getMovedToGlobalSettings(sSystemMovedToGlobalSettings);
     }
 
-    /**
-     * Get the content URI of a row added to a table.
-     * @param tableUri of the entire table
-     * @param values found in the row
-     * @param rowId of the row
-     * @return the content URI for this particular row
-     */
-    private Uri getUriFor(Uri tableUri, ContentValues values, long rowId) {
-        if (tableUri.getPathSegments().size() != 1) {
-            throw new IllegalArgumentException("Invalid URI: " + tableUri);
-        }
-        String table = tableUri.getPathSegments().get(0);
-        if (TABLE_SYSTEM.equals(table) ||
-                TABLE_SECURE.equals(table) ||
-                TABLE_GLOBAL.equals(table)) {
-            String name = values.getAsString(Settings.NameValueTable.NAME);
-            return Uri.withAppendedPath(tableUri, name);
-        } else {
-            return ContentUris.withAppendedId(tableUri, rowId);
-        }
+    // Per user system settings that moved to the per user secure settings.
+    static final Set<String> sSystemMovedToSecureSettings = new ArraySet<>();
+    static {
+        Settings.System.getMovedToSecureSettings(sSystemMovedToSecureSettings);
     }
 
-    /**
-     * Send a notification when a particular content URI changes.
-     * Modify the system property used to communicate the version of
-     * this table, for tables which have such a property.  (The Settings
-     * contract class uses these to provide client-side caches.)
-     * @param uri to send notifications for
-     */
-    private void sendNotify(Uri uri, int userHandle) {
-        // Update the system property *first*, so if someone is listening for
-        // a notification and then using the contract class to get their data,
-        // the system property will be updated and they'll get the new data.
-
-        boolean backedUpDataChanged = false;
-        String property = null, table = uri.getPathSegments().get(0);
-        final boolean isGlobal = table.equals(TABLE_GLOBAL);
-        if (table.equals(TABLE_SYSTEM)) {
-            property = Settings.System.SYS_PROP_SETTING_VERSION;
-            backedUpDataChanged = true;
-        } else if (table.equals(TABLE_SECURE)) {
-            property = Settings.Secure.SYS_PROP_SETTING_VERSION;
-            backedUpDataChanged = true;
-        } else if (isGlobal) {
-            property = Settings.Global.SYS_PROP_SETTING_VERSION;    // this one is global
-            backedUpDataChanged = true;
-        }
-
-        if (property != null) {
-            long version = SystemProperties.getLong(property, 0) + 1;
-            if (LOCAL_LOGV) Log.v(TAG, "property: " + property + "=" + version);
-            SystemProperties.set(property, Long.toString(version));
-        }
-
-        // Inform the backup manager about a data change
-        if (backedUpDataChanged) {
-            mBackupManager.dataChanged();
-        }
-        // Now send the notification through the content framework.
-
-        String notify = uri.getQueryParameter("notify");
-        if (notify == null || "true".equals(notify)) {
-            final int notifyTarget = isGlobal ? UserHandle.USER_ALL : userHandle;
-            final long oldId = Binder.clearCallingIdentity();
-            try {
-                getContext().getContentResolver().notifyChange(uri, null, true, notifyTarget);
-            } finally {
-                Binder.restoreCallingIdentity(oldId);
-            }
-            if (LOCAL_LOGV) Log.v(TAG, "notifying for " + notifyTarget + ": " + uri);
-        } else {
-            if (LOCAL_LOGV) Log.v(TAG, "notification suppressed: " + uri);
-        }
+    // Per all users global settings that moved to the per user secure settings.
+    static final Set<String> sGlobalMovedToSecureSettings = new ArraySet<>();
+    static {
+        Settings.Global.getMovedToSecureSettings(sGlobalMovedToSecureSettings);
     }
 
-    /**
-     * Make sure the caller has permission to write this data.
-     * @param args supplied by the caller
-     * @throws SecurityException if the caller is forbidden to write.
-     */
-    private void checkWritePermissions(SqlArguments args) {
-        if ((TABLE_SECURE.equals(args.table) || TABLE_GLOBAL.equals(args.table)) &&
-            getContext().checkCallingOrSelfPermission(
-                    android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
-            PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    String.format("Permission denial: writing to secure settings requires %1$s",
-                                  android.Manifest.permission.WRITE_SECURE_SETTINGS));
-        }
+    // Per user secure settings that are cloned for the managed profiles of the user.
+    private static final Set<String> sSecureCloneToManagedSettings = new ArraySet<>();
+    static {
+        Settings.Secure.getCloneToManagedProfileSettings(sSecureCloneToManagedSettings);
     }
 
-    private void checkUserRestrictions(String setting, int userId) {
-        String userRestriction = sRestrictedKeys.get(setting);
-        if (!TextUtils.isEmpty(userRestriction)
-            && mUserManager.hasUserRestriction(userRestriction, new UserHandle(userId))) {
-            throw new SecurityException(
-                    "Permission denial: user is restricted from changing this setting.");
-        }
+    // Per user system settings that are cloned for the managed profiles of the user.
+    private static final Set<String> sSystemCloneToManagedSettings = new ArraySet<>();
+    static {
+        Settings.System.getCloneToManagedProfileSettings(sSystemCloneToManagedSettings);
     }
 
-    // FileObserver for external modifications to the database file.
-    // Note that this is for platform developers only with
-    // userdebug/eng builds who should be able to tinker with the
-    // sqlite database out from under the SettingsProvider, which is
-    // normally the exclusive owner of the database.  But we keep this
-    // enabled all the time to minimize development-vs-user
-    // differences in testing.
-    private static SparseArray<SettingsFileObserver> sObserverInstances
-            = new SparseArray<SettingsFileObserver>();
-    private class SettingsFileObserver extends FileObserver {
-        private final AtomicBoolean mIsDirty = new AtomicBoolean(false);
-        private final int mUserHandle;
-        private final String mPath;
+    private final Object mLock = new Object();
 
-        public SettingsFileObserver(int userHandle, String path) {
-            super(path, FileObserver.CLOSE_WRITE |
-                  FileObserver.CREATE | FileObserver.DELETE |
-                  FileObserver.MOVED_TO | FileObserver.MODIFY);
-            mUserHandle = userHandle;
-            mPath = path;
-        }
+    @GuardedBy("mLock")
+    private SettingsRegistry mSettingsRegistry;
 
-        public void onEvent(int event, String path) {
-            final AtomicInteger mutationCount;
-            synchronized (SettingsProvider.this) {
-                mutationCount = sKnownMutationsInFlight.get(mUserHandle);
-            }
-            if (mutationCount != null && mutationCount.get() > 0) {
-                // our own modification.
-                return;
-            }
-            Log.d(TAG, "User " + mUserHandle + " external modification to " + mPath
-                    + "; event=" + event);
-            if (!mIsDirty.compareAndSet(false, true)) {
-                // already handled. (we get a few update events
-                // during an sqlite write)
-                return;
-            }
-            Log.d(TAG, "User " + mUserHandle + " updating our caches for " + mPath);
-            fullyPopulateCaches(mUserHandle);
-            mIsDirty.set(false);
-        }
-    }
+    @GuardedBy("mLock")
+    private UserManager mUserManager;
+
+    @GuardedBy("mLock")
+    private AppOpsManager mAppOpsManager;
+
+    @GuardedBy("mLock")
+    private PackageManager mPackageManager;
 
     @Override
     public boolean onCreate() {
-        mBackupManager = new BackupManager(getContext());
-        mUserManager = UserManager.get(getContext());
-
-        setAppOps(AppOpsManager.OP_NONE, AppOpsManager.OP_WRITE_SETTINGS);
-        establishDbTracking(UserHandle.USER_OWNER);
-
-        IntentFilter userFilter = new IntentFilter();
-        userFilter.addAction(Intent.ACTION_USER_REMOVED);
-        userFilter.addAction(Intent.ACTION_USER_ADDED);
-        getContext().registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_OWNER);
-                if (intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
-                    onUserRemoved(userHandle);
-                } else if (intent.getAction().equals(Intent.ACTION_USER_ADDED)) {
-                    onProfilesChanged();
-                }
-            }
-        }, userFilter);
-
-        onProfilesChanged();
-
+        synchronized (mLock) {
+            mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+            mAppOpsManager = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+            mPackageManager = getContext().getPackageManager();
+            mSettingsRegistry = new SettingsRegistry();
+        }
+        registerBroadcastReceivers();
         return true;
     }
 
-    void onUserRemoved(int userHandle) {
-        synchronized (this) {
-            // the db file itself will be deleted automatically, but we need to tear down
-            // our caches and other internal bookkeeping.
-            FileObserver observer = sObserverInstances.get(userHandle);
-            if (observer != null) {
-                observer.stopWatching();
-                sObserverInstances.delete(userHandle);
-            }
-
-            mOpenHelpers.delete(userHandle);
-            sSystemCaches.delete(userHandle);
-            sSecureCaches.delete(userHandle);
-            sKnownMutationsInFlight.delete(userHandle);
-            onProfilesChanged();
-        }
-    }
-
-    /**
-     * Updates the list of managed profiles. It assumes that only the primary user
-     * can have managed profiles. Modify this code if that changes in the future.
-     */
-    void onProfilesChanged() {
-        synchronized (this) {
-            mManagedProfiles = mUserManager.getProfiles(UserHandle.USER_OWNER);
-            if (mManagedProfiles != null) {
-                // Remove the primary user from the list
-                for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
-                    if (mManagedProfiles.get(i).id == UserHandle.USER_OWNER) {
-                        mManagedProfiles.remove(i);
-                    }
-                }
-                // If there are no managed profiles, reset the variable
-                if (mManagedProfiles.size() == 0) {
-                    mManagedProfiles = null;
-                }
-            }
-            if (LOCAL_LOGV) {
-                Slog.d(TAG, "Managed Profiles = " + mManagedProfiles);
-            }
-        }
-    }
-
-    private void establishDbTracking(int userHandle) {
-        if (LOCAL_LOGV) {
-            Slog.i(TAG, "Installing settings db helper and caches for user " + userHandle);
-        }
-
-        DatabaseHelper dbhelper;
-
-        synchronized (this) {
-            dbhelper = mOpenHelpers.get(userHandle);
-            if (dbhelper == null) {
-                dbhelper = new DatabaseHelper(getContext(), userHandle);
-                mOpenHelpers.append(userHandle, dbhelper);
-
-                sSystemCaches.append(userHandle, new SettingsCache(TABLE_SYSTEM));
-                sSecureCaches.append(userHandle, new SettingsCache(TABLE_SECURE));
-                sKnownMutationsInFlight.append(userHandle, new AtomicInteger(0));
-            }
-        }
-
-        // Initialization of the db *outside* the locks.  It's possible that racing
-        // threads might wind up here, the second having read the cache entries
-        // written by the first, but that's benign: the SQLite helper implementation
-        // manages concurrency itself, and it's important that we not run the db
-        // initialization with any of our own locks held, so we're fine.
-        SQLiteDatabase db = dbhelper.getWritableDatabase();
-
-        // Watch for external modifications to the database files,
-        // keeping our caches in sync.  We synchronize the observer set
-        // separately, and of course it has to run after the db file
-        // itself was set up by the DatabaseHelper.
-        synchronized (sObserverInstances) {
-            if (sObserverInstances.get(userHandle) == null) {
-                SettingsFileObserver observer = new SettingsFileObserver(userHandle, db.getPath());
-                sObserverInstances.append(userHandle, observer);
-                observer.startWatching();
-            }
-        }
-
-        ensureAndroidIdIsSet(userHandle);
-
-        startAsyncCachePopulation(userHandle);
-    }
-
-    class CachePrefetchThread extends Thread {
-        private int mUserHandle;
-
-        CachePrefetchThread(int userHandle) {
-            super("populate-settings-caches");
-            mUserHandle = userHandle;
-        }
-
-        @Override
-        public void run() {
-            fullyPopulateCaches(mUserHandle);
-        }
-    }
-
-    private void startAsyncCachePopulation(int userHandle) {
-        new CachePrefetchThread(userHandle).start();
-    }
-
-    private void fullyPopulateCaches(final int userHandle) {
-        DatabaseHelper dbHelper;
-        synchronized (this) {
-            dbHelper = mOpenHelpers.get(userHandle);
-        }
-        if (dbHelper == null) {
-            // User is gone.
-            return;
-        }
-        // Only populate the globals cache once, for the owning user
-        if (userHandle == UserHandle.USER_OWNER) {
-            fullyPopulateCache(dbHelper, TABLE_GLOBAL, sGlobalCache);
-        }
-        fullyPopulateCache(dbHelper, TABLE_SECURE, sSecureCaches.get(userHandle));
-        fullyPopulateCache(dbHelper, TABLE_SYSTEM, sSystemCaches.get(userHandle));
-    }
-
-    // Slurp all values (if sane in number & size) into cache.
-    private void fullyPopulateCache(DatabaseHelper dbHelper, String table, SettingsCache cache) {
-        SQLiteDatabase db = dbHelper.getReadableDatabase();
-        Cursor c = db.query(
-            table,
-            new String[] { Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE },
-            null, null, null, null, null,
-            "" + (MAX_CACHE_ENTRIES + 1) /* limit */);
-        try {
-            synchronized (cache) {
-                cache.evictAll();
-                cache.setFullyMatchesDisk(true);  // optimistic
-                int rows = 0;
-                while (c.moveToNext()) {
-                    rows++;
-                    String name = c.getString(0);
-                    String value = c.getString(1);
-                    cache.populate(name, value);
-                }
-                if (rows > MAX_CACHE_ENTRIES) {
-                    // Somewhat redundant, as removeEldestEntry() will
-                    // have already done this, but to be explicit:
-                    cache.setFullyMatchesDisk(false);
-                    Log.d(TAG, "row count exceeds max cache entries for table " + table);
-                }
-                if (LOCAL_LOGV) Log.d(TAG, "cache for settings table '" + table
-                        + "' rows=" + rows + "; fullycached=" + cache.fullyMatchesDisk());
-            }
-        } finally {
-            c.close();
-        }
-    }
-
-    private boolean ensureAndroidIdIsSet(int userHandle) {
-        final Cursor c = queryForUser(Settings.Secure.CONTENT_URI,
-                new String[] { Settings.NameValueTable.VALUE },
-                Settings.NameValueTable.NAME + "=?",
-                new String[] { Settings.Secure.ANDROID_ID }, null,
-                userHandle);
-        try {
-            final String value = c.moveToNext() ? c.getString(0) : null;
-            if (value == null) {
-                // sanity-check the user before touching the db
-                final UserInfo user = mUserManager.getUserInfo(userHandle);
-                if (user == null) {
-                    // can happen due to races when deleting users; treat as benign
-                    return false;
+    @Override
+    public Bundle call(String method, String name, Bundle args) {
+        synchronized (mLock) {
+            final int requestingUserId = getRequestingUserId(args);
+            switch (method) {
+                case Settings.CALL_METHOD_GET_GLOBAL: {
+                    Setting setting = getGlobalSettingLocked(name);
+                    return packageValueForCallResult(setting);
                 }
 
-                final SecureRandom random = new SecureRandom();
-                final String newAndroidIdValue = Long.toHexString(random.nextLong());
-                final ContentValues values = new ContentValues();
-                values.put(Settings.NameValueTable.NAME, Settings.Secure.ANDROID_ID);
-                values.put(Settings.NameValueTable.VALUE, newAndroidIdValue);
-                final Uri uri = insertForUser(Settings.Secure.CONTENT_URI, values, userHandle);
-                if (uri == null) {
-                    Slog.e(TAG, "Unable to generate new ANDROID_ID for user " + userHandle);
-                    return false;
+                case Settings.CALL_METHOD_GET_SECURE: {
+                    Setting setting = getSecureSettingLocked(name, requestingUserId);
+                    return packageValueForCallResult(setting);
                 }
-                Slog.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue
-                        + "] for user " + userHandle);
-                // Write a dropbox entry if it's a restricted profile
-                if (user.isRestricted()) {
-                    DropBoxManager dbm = (DropBoxManager)
-                            getContext().getSystemService(Context.DROPBOX_SERVICE);
-                    if (dbm != null && dbm.isTagEnabled(DROPBOX_TAG_USERLOG)) {
-                        dbm.addText(DROPBOX_TAG_USERLOG, System.currentTimeMillis()
-                                + ",restricted_profile_ssaid,"
-                                + newAndroidIdValue + "\n");
-                    }
+
+                case Settings.CALL_METHOD_GET_SYSTEM: {
+                    Setting setting = getSystemSettingLocked(name, requestingUserId);
+                    return packageValueForCallResult(setting);
                 }
-            }
-            return true;
-        } finally {
-            c.close();
-        }
-    }
 
-    // Lazy-initialize the settings caches for non-primary users
-    private SettingsCache getOrConstructCache(int callingUser, SparseArray<SettingsCache> which) {
-        getOrEstablishDatabase(callingUser); // ignore return value; we don't need it
-        return which.get(callingUser);
-    }
+                case Settings.CALL_METHOD_PUT_GLOBAL: {
+                    String value = getSettingValue(args);
+                    insertGlobalSettingLocked(name, value, requestingUserId);
+                } break;
 
-    // Lazy initialize the database helper and caches for this user, if necessary
-    private DatabaseHelper getOrEstablishDatabase(int callingUser) {
-        if (callingUser >= Process.SYSTEM_UID) {
-            if (USER_CHECK_THROWS) {
-                throw new IllegalArgumentException("Uid rather than user handle: " + callingUser);
-            } else {
-                Slog.wtf(TAG, "establish db for uid rather than user: " + callingUser);
-            }
-        }
+                case Settings.CALL_METHOD_PUT_SECURE: {
+                    String value = getSettingValue(args);
+                    insertSecureSettingLocked(name, value, requestingUserId);
+                } break;
 
-        long oldId = Binder.clearCallingIdentity();
-        try {
-            DatabaseHelper dbHelper;
-            synchronized (this) {
-                dbHelper = mOpenHelpers.get(callingUser);
-            }
-            if (null == dbHelper) {
-                establishDbTracking(callingUser);
-                synchronized (this) {
-                    dbHelper = mOpenHelpers.get(callingUser);
-                }
-            }
-            return dbHelper;
-        } finally {
-            Binder.restoreCallingIdentity(oldId);
-        }
-    }
+                case Settings.CALL_METHOD_PUT_SYSTEM: {
+                    String value = getSettingValue(args);
+                    insertSystemSettingLocked(name, value, requestingUserId);
+                } break;
 
-    public SettingsCache cacheForTable(final int callingUser, String tableName) {
-        if (TABLE_SYSTEM.equals(tableName)) {
-            return getOrConstructCache(callingUser, sSystemCaches);
-        }
-        if (TABLE_SECURE.equals(tableName)) {
-            return getOrConstructCache(callingUser, sSecureCaches);
-        }
-        if (TABLE_GLOBAL.equals(tableName)) {
-            return sGlobalCache;
+                default: {
+                    Slog.w(LOG_TAG, "call() with invalid method: " + method);
+                } break;
+            }
         }
         return null;
     }
 
-    /**
-     * Used for wiping a whole cache on deletes when we're not
-     * sure what exactly was deleted or changed.
-     */
-    public void invalidateCache(final int callingUser, String tableName) {
-        SettingsCache cache = cacheForTable(callingUser, tableName);
-        if (cache == null) {
-            return;
-        }
-        synchronized (cache) {
-            cache.evictAll();
-            cache.mCacheFullyMatchesDisk = false;
-        }
-    }
-
-    /**
-     * Checks if the calling user is a managed profile of the primary user.
-     * Currently only the primary user (USER_OWNER) can have managed profiles.
-     * @param callingUser the user trying to read/write settings
-     * @return true if it is a managed profile of the primary user
-     */
-    private boolean isManagedProfile(int callingUser) {
-        synchronized (this) {
-            if (mManagedProfiles == null) return false;
-            for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
-                if (mManagedProfiles.get(i).id == callingUser) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Fast path that avoids the use of chatty remoted Cursors.
-     */
     @Override
-    public Bundle call(String method, String request, Bundle args) {
-        int callingUser = UserHandle.getCallingUserId();
-        if (args != null) {
-            int reqUser = args.getInt(Settings.CALL_METHOD_USER_KEY, callingUser);
-            if (reqUser != callingUser) {
-                callingUser = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
-                        Binder.getCallingUid(), reqUser, false, true,
-                        "get/set setting for user", null);
-                if (LOCAL_LOGV) Slog.v(TAG, "   access setting for user " + callingUser);
-            }
-        }
-
-        // Note: we assume that get/put operations for moved-to-global names have already
-        // been directed to the new location on the caller side (otherwise we'd fix them
-        // up here).
-        DatabaseHelper dbHelper;
-        SettingsCache cache;
-
-        // Get methods
-        if (Settings.CALL_METHOD_GET_SYSTEM.equals(method)) {
-            if (LOCAL_LOGV) Slog.v(TAG, "call(system:" + request + ") for " + callingUser);
-            // Check if this request should be (re)directed to the primary user's db
-            if (callingUser != UserHandle.USER_OWNER
-                    && shouldShadowParentProfile(callingUser, sSystemCloneToManagedKeys, request)) {
-                callingUser = UserHandle.USER_OWNER;
-            }
-            dbHelper = getOrEstablishDatabase(callingUser);
-            cache = sSystemCaches.get(callingUser);
-            return lookupValue(dbHelper, TABLE_SYSTEM, cache, request);
-        }
-        if (Settings.CALL_METHOD_GET_SECURE.equals(method)) {
-            if (LOCAL_LOGV) Slog.v(TAG, "call(secure:" + request + ") for " + callingUser);
-            // Check if this is a setting to be copied from the primary user
-            if (shouldShadowParentProfile(callingUser, sSecureCloneToManagedKeys, request)) {
-                // If the request if for location providers and there's a restriction, return none
-                if (Secure.LOCATION_PROVIDERS_ALLOWED.equals(request)
-                        && mUserManager.hasUserRestriction(
-                                UserManager.DISALLOW_SHARE_LOCATION, new UserHandle(callingUser))) {
-                    return sSecureCaches.get(callingUser).putIfAbsent(request, "");
-                }
-                callingUser = UserHandle.USER_OWNER;
-            }
-            dbHelper = getOrEstablishDatabase(callingUser);
-            cache = sSecureCaches.get(callingUser);
-            return lookupValue(dbHelper, TABLE_SECURE, cache, request);
-        }
-        if (Settings.CALL_METHOD_GET_GLOBAL.equals(method)) {
-            if (LOCAL_LOGV) Slog.v(TAG, "call(global:" + request + ") for " + callingUser);
-            // fast path: owner db & cache are immutable after onCreate() so we need not
-            // guard on the attempt to look them up
-            return lookupValue(getOrEstablishDatabase(UserHandle.USER_OWNER), TABLE_GLOBAL,
-                    sGlobalCache, request);
-        }
-
-        // Put methods - new value is in the args bundle under the key named by
-        // the Settings.NameValueTable.VALUE static.
-        final String newValue = (args == null)
-                ? null : args.getString(Settings.NameValueTable.VALUE);
-
-        // Framework can't do automatic permission checking for calls, so we need
-        // to do it here.
-        if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    String.format("Permission denial: writing to settings requires %1$s",
-                                  android.Manifest.permission.WRITE_SETTINGS));
-        }
-
-        // Also need to take care of app op.
-        if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SETTINGS, Binder.getCallingUid(),
-                getCallingPackage()) != AppOpsManager.MODE_ALLOWED) {
-            return null;
-        }
-
-        final ContentValues values = new ContentValues();
-        values.put(Settings.NameValueTable.NAME, request);
-        values.put(Settings.NameValueTable.VALUE, newValue);
-        if (Settings.CALL_METHOD_PUT_SYSTEM.equals(method)) {
-            if (LOCAL_LOGV) {
-                Slog.v(TAG, "call_put(system:" + request + "=" + newValue + ") for "
-                        + callingUser);
-            }
-            // Extra check for USER_OWNER to optimize for the 99%
-            if (callingUser != UserHandle.USER_OWNER && shouldShadowParentProfile(callingUser,
-                    sSystemCloneToManagedKeys, request)) {
-                // Don't write these settings, as they are cloned from the parent profile
-                return null;
-            }
-            insertForUser(Settings.System.CONTENT_URI, values, callingUser);
-            // Clone the settings to the managed profiles so that notifications can be sent out
-            if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null
-                    && sSystemCloneToManagedKeys.contains(request)) {
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
-                        if (LOCAL_LOGV) {
-                            Slog.v(TAG, "putting to additional user "
-                                    + mManagedProfiles.get(i).id);
-                        }
-                        insertForUser(Settings.System.CONTENT_URI, values,
-                                mManagedProfiles.get(i).id);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-            }
-        } else if (Settings.CALL_METHOD_PUT_SECURE.equals(method)) {
-            if (LOCAL_LOGV) {
-                Slog.v(TAG, "call_put(secure:" + request + "=" + newValue + ") for "
-                        + callingUser);
-            }
-            // Extra check for USER_OWNER to optimize for the 99%
-            if (callingUser != UserHandle.USER_OWNER && shouldShadowParentProfile(callingUser,
-                    sSecureCloneToManagedKeys, request)) {
-                // Don't write these settings, as they are cloned from the parent profile
-                return null;
-            }
-            insertForUser(Settings.Secure.CONTENT_URI, values, callingUser);
-            // Clone the settings to the managed profiles so that notifications can be sent out
-            if (callingUser == UserHandle.USER_OWNER && mManagedProfiles != null
-                    && sSecureCloneToManagedKeys.contains(request)) {
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    for (int i = mManagedProfiles.size() - 1; i >= 0; i--) {
-                        if (LOCAL_LOGV) {
-                            Slog.v(TAG, "putting to additional user "
-                                    + mManagedProfiles.get(i).id);
-                        }
-                        try {
-                            insertForUser(Settings.Secure.CONTENT_URI, values,
-                                    mManagedProfiles.get(i).id);
-                        } catch (SecurityException e) {
-                            // Temporary fix, see b/17450158
-                            Slog.w(TAG, "Cannot clone request '" + request + "' with value '"
-                                    + newValue + "' to managed profile (id "
-                                    + mManagedProfiles.get(i).id + ")", e);
-                        }
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-            }
-        } else if (Settings.CALL_METHOD_PUT_GLOBAL.equals(method)) {
-            if (LOCAL_LOGV) {
-                Slog.v(TAG, "call_put(global:" + request + "=" + newValue + ") for "
-                        + callingUser);
-            }
-            insertForUser(Settings.Global.CONTENT_URI, values, callingUser);
-        } else {
-            Slog.w(TAG, "call() with invalid method: " + method);
-        }
-
-        return null;
-    }
-
-    /**
-     * Check if the user is a managed profile and name is one of the settings to be cloned
-     * from the parent profile.
-     */
-    private boolean shouldShadowParentProfile(int userId, HashSet<String> keys, String name) {
-        return isManagedProfile(userId) && keys.contains(name);
-    }
-
-    // Looks up value 'key' in 'table' and returns either a single-pair Bundle,
-    // possibly with a null value, or null on failure.
-    private Bundle lookupValue(DatabaseHelper dbHelper, String table,
-            final SettingsCache cache, String key) {
-        if (cache == null) {
-           Slog.e(TAG, "cache is null for user " + UserHandle.getCallingUserId() + " : key=" + key);
-           return null;
-        }
-        synchronized (cache) {
-            Bundle value = cache.get(key);
-            if (value != null) {
-                if (value != TOO_LARGE_TO_CACHE_MARKER) {
-                    return value;
-                }
-                // else we fall through and read the value from disk
-            } else if (cache.fullyMatchesDisk()) {
-                // Fast path (very common).  Don't even try touch disk
-                // if we know we've slurped it all in.  Trying to
-                // touch the disk would mean waiting for yaffs2 to
-                // give us access, which could takes hundreds of
-                // milliseconds.  And we're very likely being called
-                // from somebody's UI thread...
-                return NULL_SETTING;
-            }
-        }
-
-        SQLiteDatabase db = dbHelper.getReadableDatabase();
-        Cursor cursor = null;
-        try {
-            cursor = db.query(table, COLUMN_VALUE, "name=?", new String[]{key},
-                              null, null, null, null);
-            if (cursor != null && cursor.getCount() == 1) {
-                cursor.moveToFirst();
-                return cache.putIfAbsent(key, cursor.getString(0));
-            }
-        } catch (SQLiteException e) {
-            Log.w(TAG, "settings lookup error", e);
-            return null;
-        } finally {
-            if (cursor != null) cursor.close();
-        }
-        cache.putIfAbsent(key, null);
-        return NULL_SETTING;
-    }
-
-    @Override
-    public Cursor query(Uri url, String[] select, String where, String[] whereArgs, String sort) {
-        return queryForUser(url, select, where, whereArgs, sort, UserHandle.getCallingUserId());
-    }
-
-    private Cursor queryForUser(Uri url, String[] select, String where, String[] whereArgs,
-            String sort, int forUser) {
-        if (LOCAL_LOGV) Slog.v(TAG, "query(" + url + ") for user " + forUser);
-        SqlArguments args = new SqlArguments(url, where, whereArgs);
-        DatabaseHelper dbH;
-        dbH = getOrEstablishDatabase(
-                TABLE_GLOBAL.equals(args.table) ? UserHandle.USER_OWNER : forUser);
-        SQLiteDatabase db = dbH.getReadableDatabase();
-
-        // The favorites table was moved from this provider to a provider inside Home
-        // Home still need to query this table to upgrade from pre-cupcake builds
-        // However, a cupcake+ build with no data does not contain this table which will
-        // cause an exception in the SQL stack. The following line is a special case to
-        // let the caller of the query have a chance to recover and avoid the exception
-        if (TABLE_FAVORITES.equals(args.table)) {
-            return null;
-        } else if (TABLE_OLD_FAVORITES.equals(args.table)) {
-            args.table = TABLE_FAVORITES;
-            Cursor cursor = db.rawQuery("PRAGMA table_info(favorites);", null);
-            if (cursor != null) {
-                boolean exists = cursor.getCount() > 0;
-                cursor.close();
-                if (!exists) return null;
-            } else {
-                return null;
-            }
-        }
-
-        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        qb.setTables(args.table);
-
-        Cursor ret = qb.query(db, select, args.where, args.args, null, null, sort);
-        // the default Cursor interface does not support per-user observation
-        try {
-            AbstractCursor c = (AbstractCursor) ret;
-            c.setNotificationUri(getContext().getContentResolver(), url, forUser);
-        } catch (ClassCastException e) {
-            // details of the concrete Cursor implementation have changed and this code has
-            // not been updated to match -- complain and fail hard.
-            Log.wtf(TAG, "Incompatible cursor derivation!");
-            throw e;
-        }
-        return ret;
-    }
-
-    @Override
-    public String getType(Uri url) {
-        // If SqlArguments supplies a where clause, then it must be an item
-        // (because we aren't supplying our own where clause).
-        SqlArguments args = new SqlArguments(url, null, null);
-        if (TextUtils.isEmpty(args.where)) {
+    public String getType(Uri uri) {
+        Arguments args = new Arguments(uri, null, null, true);
+        if (TextUtils.isEmpty(args.name)) {
             return "vnd.android.cursor.dir/" + args.table;
         } else {
-            return "vnd.android.cursor.item/" + args.table;
+                return "vnd.android.cursor.item/" + args.table;
         }
     }
 
     @Override
-    public int bulkInsert(Uri uri, ContentValues[] values) {
-        final int callingUser = UserHandle.getCallingUserId();
-        if (LOCAL_LOGV) Slog.v(TAG, "bulkInsert() for user " + callingUser);
-        SqlArguments args = new SqlArguments(uri);
-        if (TABLE_FAVORITES.equals(args.table)) {
-            return 0;
-        }
-        checkWritePermissions(args);
-        SettingsCache cache = cacheForTable(callingUser, args.table);
-
-        final AtomicInteger mutationCount;
-        synchronized (this) {
-            mutationCount = sKnownMutationsInFlight.get(callingUser);
-        }
-        if (mutationCount != null) {
-            mutationCount.incrementAndGet();
-        }
-        DatabaseHelper dbH = getOrEstablishDatabase(
-                TABLE_GLOBAL.equals(args.table) ? UserHandle.USER_OWNER : callingUser);
-        SQLiteDatabase db = dbH.getWritableDatabase();
-        db.beginTransaction();
-        try {
-            int numValues = values.length;
-            for (int i = 0; i < numValues; i++) {
-                checkUserRestrictions(values[i].getAsString(Settings.Secure.NAME), callingUser);
-                if (db.insert(args.table, null, values[i]) < 0) return 0;
-                SettingsCache.populate(cache, values[i]);
-                if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + values[i]);
-            }
-            db.setTransactionSuccessful();
-        } finally {
-            db.endTransaction();
-            if (mutationCount != null) {
-                mutationCount.decrementAndGet();
-            }
+    public Cursor query(Uri uri, String[] projection, String where, String[] whereArgs,
+            String order) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "query() for user: " + UserHandle.getCallingUserId());
         }
 
-        sendNotify(uri, callingUser);
-        return values.length;
-    }
+        Arguments args = new Arguments(uri, where, whereArgs, true);
+        String[] normalizedProjection = normalizeProjection(projection);
 
-    /*
-     * Used to parse changes to the value of Settings.Secure.LOCATION_PROVIDERS_ALLOWED.
-     * This setting contains a list of the currently enabled location providers.
-     * But helper functions in android.providers.Settings can enable or disable
-     * a single provider by using a "+" or "-" prefix before the provider name.
-     *
-     * @returns whether the database needs to be updated or not, also modifying
-     *     'initialValues' if needed.
-     */
-    private boolean parseProviderList(Uri url, ContentValues initialValues, int desiredUser) {
-        String value = initialValues.getAsString(Settings.Secure.VALUE);
-        String newProviders = null;
-        if (value != null && value.length() > 1) {
-            char prefix = value.charAt(0);
-            if (prefix == '+' || prefix == '-') {
-                // skip prefix
-                value = value.substring(1);
+        // If a legacy table that is gone, done.
+        if (REMOVED_LEGACY_TABLES.contains(args.table)) {
+            return new MatrixCursor(normalizedProjection, 0);
+        }
 
-                // read list of enabled providers into "providers"
-                String providers = "";
-                String[] columns = {Settings.Secure.VALUE};
-                String where = Settings.Secure.NAME + "=\'" + Settings.Secure.LOCATION_PROVIDERS_ALLOWED + "\'";
-                Cursor cursor = queryForUser(url, columns, where, null, null, desiredUser);
-                if (cursor != null && cursor.getCount() == 1) {
-                    try {
-                        cursor.moveToFirst();
-                        providers = cursor.getString(0);
-                    } finally {
-                        cursor.close();
-                    }
-                }
-
-                int index = providers.indexOf(value);
-                int end = index + value.length();
-                // check for commas to avoid matching on partial string
-                if (index > 0 && providers.charAt(index - 1) != ',') index = -1;
-                if (end < providers.length() && providers.charAt(end) != ',') index = -1;
-
-                if (prefix == '+' && index < 0) {
-                    // append the provider to the list if not present
-                    if (providers.length() == 0) {
-                        newProviders = value;
+        synchronized (mLock) {
+            switch (args.table) {
+                case TABLE_GLOBAL: {
+                    if (args.name != null) {
+                        Setting setting = getGlobalSettingLocked(args.name);
+                        return packageSettingForQuery(setting, normalizedProjection);
                     } else {
-                        newProviders = providers + ',' + value;
+                        return getAllGlobalSettingsLocked(projection);
                     }
-                } else if (prefix == '-' && index >= 0) {
-                    // remove the provider from the list if present
-                    // remove leading or trailing comma
-                    if (index > 0) {
-                        index--;
-                    } else if (end < providers.length()) {
-                        end++;
-                    }
-
-                    newProviders = providers.substring(0, index);
-                    if (end < providers.length()) {
-                        newProviders += providers.substring(end);
-                    }
-                } else {
-                    // nothing changed, so no need to update the database
-                    return false;
                 }
 
-                if (newProviders != null) {
-                    initialValues.put(Settings.Secure.VALUE, newProviders);
+                case TABLE_SECURE: {
+                    final int userId = UserHandle.getCallingUserId();
+                    if (args.name != null) {
+                        Setting setting = getSecureSettingLocked(args.name, userId);
+                        return packageSettingForQuery(setting, normalizedProjection);
+                    } else {
+                        return getAllSecureSettingsLocked(userId, projection);
+                    }
+                }
+
+                case TABLE_SYSTEM: {
+                    final int userId = UserHandle.getCallingUserId();
+                    if (args.name != null) {
+                        Setting setting = getSystemSettingLocked(args.name, userId);
+                        return packageSettingForQuery(setting, normalizedProjection);
+                    } else {
+                        return getAllSystemSettingsLocked(userId, projection);
+                    }
+                }
+
+                default: {
+                    throw new IllegalArgumentException("Invalid Uri path:" + uri);
                 }
             }
         }
-
-        return true;
     }
 
     @Override
-    public Uri insert(Uri url, ContentValues initialValues) {
-        return insertForUser(url, initialValues, UserHandle.getCallingUserId());
-    }
-
-    // Settings.put*ForUser() always winds up here, so this is where we apply
-    // policy around permission to write settings for other users.
-    private Uri insertForUser(Uri url, ContentValues initialValues, int desiredUserHandle) {
-        final int callingUser = UserHandle.getCallingUserId();
-        if (callingUser != desiredUserHandle) {
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "Not permitted to access settings for other users");
+    public Uri insert(Uri uri, ContentValues values) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "insert() for user: " + UserHandle.getCallingUserId());
         }
 
-        if (LOCAL_LOGV) Slog.v(TAG, "insert(" + url + ") for user " + desiredUserHandle
-                + " by " + callingUser);
+        String table = getValidTableOrThrow(uri);
 
-        SqlArguments args = new SqlArguments(url);
-        if (TABLE_FAVORITES.equals(args.table)) {
+        // If a legacy table that is gone, done.
+        if (REMOVED_LEGACY_TABLES.contains(table)) {
             return null;
         }
 
-        // Special case LOCATION_PROVIDERS_ALLOWED.
-        // Support enabling/disabling a single provider (using "+" or "-" prefix)
-        String name = initialValues.getAsString(Settings.Secure.NAME);
-        if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
-            if (!parseProviderList(url, initialValues, desiredUserHandle)) return null;
+        String name = values.getAsString(Settings.Secure.NAME);
+        if (TextUtils.isEmpty(name)) {
+            return null;
         }
 
-        // If this is an insert() of a key that has been migrated to the global store,
-        // redirect the operation to that store
-        if (name != null) {
-            if (sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name)) {
-                if (!TABLE_GLOBAL.equals(args.table)) {
-                    if (LOCAL_LOGV) Slog.i(TAG, "Rewrite of insert() of now-global key " + name);
+        String value = values.getAsString(Settings.Secure.VALUE);
+
+        synchronized (mLock) {
+            switch (table) {
+                case TABLE_GLOBAL: {
+                    if (insertGlobalSettingLocked(name, value, UserHandle.getCallingUserId())) {
+                        return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
+                    }
+                } break;
+
+                case TABLE_SECURE: {
+                    if (insertSecureSettingLocked(name, value, UserHandle.getCallingUserId())) {
+                        return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
+                    }
+                } break;
+
+                case TABLE_SYSTEM: {
+                    if (insertSystemSettingLocked(name, value, UserHandle.getCallingUserId())) {
+                        return Uri.withAppendedPath(Settings.System.CONTENT_URI, name);
+                    }
+                } break;
+
+                default: {
+                    throw new IllegalArgumentException("Bad Uri path:" + uri);
                 }
-                args.table = TABLE_GLOBAL;  // next condition will rewrite the user handle
             }
         }
 
-        // Check write permissions only after determining which table the insert will touch
-        checkWritePermissions(args);
-
-        checkUserRestrictions(name, desiredUserHandle);
-
-        // The global table is stored under the owner, always
-        if (TABLE_GLOBAL.equals(args.table)) {
-            desiredUserHandle = UserHandle.USER_OWNER;
-        }
-
-        SettingsCache cache = cacheForTable(desiredUserHandle, args.table);
-        String value = initialValues.getAsString(Settings.NameValueTable.VALUE);
-        if (SettingsCache.isRedundantSetValue(cache, name, value)) {
-            return Uri.withAppendedPath(url, name);
-        }
-
-        final AtomicInteger mutationCount;
-        synchronized (this) {
-            mutationCount = sKnownMutationsInFlight.get(callingUser);
-        }
-        if (mutationCount != null) {
-            mutationCount.incrementAndGet();
-        }
-        DatabaseHelper dbH = getOrEstablishDatabase(desiredUserHandle);
-        SQLiteDatabase db = dbH.getWritableDatabase();
-        final long rowId = db.insert(args.table, null, initialValues);
-        if (mutationCount != null) {
-            mutationCount.decrementAndGet();
-        }
-        if (rowId <= 0) return null;
-
-        SettingsCache.populate(cache, initialValues);  // before we notify
-
-        if (LOCAL_LOGV) Log.v(TAG, args.table + " <- " + initialValues
-                + " for user " + desiredUserHandle);
-        // Note that we use the original url here, not the potentially-rewritten table name
-        url = getUriFor(url, initialValues, rowId);
-        sendNotify(url, desiredUserHandle);
-        return url;
+        return null;
     }
 
     @Override
-    public int delete(Uri url, String where, String[] whereArgs) {
-        int callingUser = UserHandle.getCallingUserId();
-        if (LOCAL_LOGV) Slog.v(TAG, "delete() for user " + callingUser);
-        SqlArguments args = new SqlArguments(url, where, whereArgs);
-        if (TABLE_FAVORITES.equals(args.table)) {
-            return 0;
-        } else if (TABLE_OLD_FAVORITES.equals(args.table)) {
-            args.table = TABLE_FAVORITES;
-        } else if (TABLE_GLOBAL.equals(args.table)) {
-            callingUser = UserHandle.USER_OWNER;
+    public int bulkInsert(Uri uri, ContentValues[] allValues) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "bulkInsert() for user: " + UserHandle.getCallingUserId());
         }
-        checkWritePermissions(args);
 
-        final AtomicInteger mutationCount;
-        synchronized (this) {
-            mutationCount = sKnownMutationsInFlight.get(callingUser);
+        int insertionCount = 0;
+        final int valuesCount = allValues.length;
+        for (int i = 0; i < valuesCount; i++) {
+            ContentValues values = allValues[i];
+            if (insert(uri, values) != null) {
+                insertionCount++;
+            }
         }
-        if (mutationCount != null) {
-            mutationCount.incrementAndGet();
-        }
-        DatabaseHelper dbH = getOrEstablishDatabase(callingUser);
-        SQLiteDatabase db = dbH.getWritableDatabase();
-        int count = db.delete(args.table, args.where, args.args);
-        if (mutationCount != null) {
-            mutationCount.decrementAndGet();
-        }
-        if (count > 0) {
-            invalidateCache(callingUser, args.table);  // before we notify
-            sendNotify(url, callingUser);
-        }
-        startAsyncCachePopulation(callingUser);
-        if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) deleted");
-        return count;
+
+        return insertionCount;
     }
 
     @Override
-    public int update(Uri url, ContentValues initialValues, String where, String[] whereArgs) {
-        // NOTE: update() is never called by the front-end Settings API, and updates that
-        // wind up affecting rows in Secure that are globally shared will not have the
-        // intended effect (the update will be invisible to the rest of the system).
-        // This should have no practical effect, since writes to the Secure db can only
-        // be done by system code, and that code should be using the correct API up front.
-        int callingUser = UserHandle.getCallingUserId();
-        if (LOCAL_LOGV) Slog.v(TAG, "update() for user " + callingUser);
-        SqlArguments args = new SqlArguments(url, where, whereArgs);
-        if (TABLE_FAVORITES.equals(args.table)) {
-            return 0;
-        } else if (TABLE_GLOBAL.equals(args.table)) {
-            callingUser = UserHandle.USER_OWNER;
+    public int delete(Uri uri, String where, String[] whereArgs) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "delete() for user: " + UserHandle.getCallingUserId());
         }
-        checkWritePermissions(args);
-        checkUserRestrictions(initialValues.getAsString(Settings.Secure.NAME), callingUser);
 
-        final AtomicInteger mutationCount;
-        synchronized (this) {
-            mutationCount = sKnownMutationsInFlight.get(callingUser);
+        Arguments args = new Arguments(uri, where, whereArgs, false);
+
+        // If a legacy table that is gone, done.
+        if (REMOVED_LEGACY_TABLES.contains(args.table)) {
+            return 0;
         }
-        if (mutationCount != null) {
-            mutationCount.incrementAndGet();
+
+        if (TextUtils.isEmpty(args.name)) {
+            return 0;
         }
-        DatabaseHelper dbH = getOrEstablishDatabase(callingUser);
-        SQLiteDatabase db = dbH.getWritableDatabase();
-        int count = db.update(args.table, initialValues, args.where, args.args);
-        if (mutationCount != null) {
-            mutationCount.decrementAndGet();
+
+        synchronized (mLock) {
+            switch (args.table) {
+                case TABLE_GLOBAL: {
+                    final int userId = UserHandle.getCallingUserId();
+                    return deleteGlobalSettingLocked(args.name, userId) ? 1 : 0;
+                }
+
+                case TABLE_SECURE: {
+                    final int userId = UserHandle.getCallingUserId();
+                    return deleteSecureSettingLocked(args.name, userId) ? 1 : 0;
+                }
+
+                case TABLE_SYSTEM: {
+                    final int userId = UserHandle.getCallingUserId();
+                    return deleteSystemSettingLocked(args.name, userId) ? 1 : 0;
+                }
+
+                default: {
+                    throw new IllegalArgumentException("Bad Uri path:" + uri);
+                }
+            }
         }
-        if (count > 0) {
-            invalidateCache(callingUser, args.table);  // before we notify
-            sendNotify(url, callingUser);
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "update() for user: " + UserHandle.getCallingUserId());
         }
-        startAsyncCachePopulation(callingUser);
-        if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) <- " + initialValues);
-        return count;
+
+        Arguments args = new Arguments(uri, where, whereArgs, false);
+
+        // If a legacy table that is gone, done.
+        if (REMOVED_LEGACY_TABLES.contains(args.table)) {
+            return 0;
+        }
+
+        String value = values.getAsString(Settings.Secure.VALUE);
+        if (TextUtils.isEmpty(value)) {
+            return 0;
+        }
+
+        synchronized (mLock) {
+            switch (args.table) {
+                case TABLE_GLOBAL: {
+                    final int userId = UserHandle.getCallingUserId();
+                    return updateGlobalSettingLocked(args.name, value, userId) ? 1 : 0;
+                }
+
+                case TABLE_SECURE: {
+                    final int userId = UserHandle.getCallingUserId();
+                    return updateSecureSettingLocked(args.name, value, userId) ? 1 : 0;
+                }
+
+                case TABLE_SYSTEM: {
+                    final int userId = UserHandle.getCallingUserId();
+                    return updateSystemSettingLocked(args.name, value, userId) ? 1 : 0;
+                }
+
+                default: {
+                    throw new IllegalArgumentException("Invalid Uri path:" + uri);
+                }
+            }
+        }
     }
 
     @Override
@@ -1229,102 +479,1360 @@
                 + "ringtone playback is available through android.media.Ringtone");
     }
 
-    /**
-     * In-memory LRU Cache of system and secure settings, along with
-     * associated helper functions to keep cache coherent with the
-     * database.
+    private void registerBroadcastReceivers() {
+        IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        userFilter.addAction(Intent.ACTION_USER_STOPPED);
+
+        getContext().registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+                        UserHandle.USER_OWNER);
+
+                switch (intent.getAction()) {
+                    case Intent.ACTION_USER_REMOVED: {
+                        mSettingsRegistry.removeUserStateLocked(userId, true);
+                    } break;
+
+                    case Intent.ACTION_USER_STOPPED: {
+                        mSettingsRegistry.removeUserStateLocked(userId, false);
+                    } break;
+                }
+            }
+        }, userFilter);
+
+        PackageMonitor monitor = new PackageMonitor() {
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                synchronized (mLock) {
+                    mSettingsRegistry.onPackageRemovedLocked(packageName,
+                            UserHandle.getUserId(uid));
+                }
+            }
+        };
+
+        // package changes
+        monitor.register(getContext(), BackgroundThread.getHandler().getLooper(),
+                UserHandle.ALL, true);
+    }
+
+    private Cursor getAllGlobalSettingsLocked(String[] projection) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "getAllGlobalSettingsLocked()");
+        }
+
+        // Get the settings.
+        SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
+                SettingsRegistry.SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
+
+        List<String> names = settingsState.getSettingNamesLocked();
+
+        final int nameCount = names.size();
+
+        String[] normalizedProjection = normalizeProjection(projection);
+        MatrixCursor result = new MatrixCursor(normalizedProjection, nameCount);
+
+        // Anyone can get the global settings, so no security checks.
+        for (int i = 0; i < nameCount; i++) {
+            String name = names.get(i);
+            Setting setting = settingsState.getSettingLocked(name);
+            appendSettingToCursor(result, setting);
+        }
+
+        return result;
+    }
+
+    private Setting getGlobalSettingLocked(String name) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "getGlobalSetting(" + name + ")");
+        }
+
+        // Get the value.
+        return mSettingsRegistry.getSettingLocked(SettingsRegistry.SETTINGS_TYPE_GLOBAL,
+                UserHandle.USER_OWNER, name);
+    }
+
+    private boolean updateGlobalSettingLocked(String name, String value, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "updateGlobalSettingLocked(" + name + ", " + value + ")");
+        }
+        return mutateGlobalSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
+    }
+
+    private boolean insertGlobalSettingLocked(String name, String value, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "insertGlobalSettingLocked(" + name + ", " + value + ")");
+        }
+        return mutateGlobalSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+    }
+
+    private boolean deleteGlobalSettingLocked(String name, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "deleteGlobalSettingLocked(" + name + ")");
+        }
+        return mutateGlobalSettingLocked(name, null, requestingUserId, MUTATION_OPERATION_DELETE);
+    }
+
+    private boolean mutateGlobalSettingLocked(String name, String value, int requestingUserId,
+            int operation) {
+        // Make sure the caller can change the settings - treated as secure.
+        enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
+
+        // Verify whether this operation is allowed for the calling package.
+        if (!isAppOpWriteSettingsAllowedForCallingPackage()) {
+            return false;
+        }
+
+        // Resolve the userId on whose behalf the call is made.
+        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+
+        // If this is a setting that is currently restricted for this user, done.
+        if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId)) {
+            return false;
+        }
+
+        // Perform the mutation.
+        switch (operation) {
+            case MUTATION_OPERATION_INSERT: {
+                return mSettingsRegistry.insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_GLOBAL,
+                        UserHandle.USER_OWNER, name, value, getCallingPackage());
+            }
+
+            case MUTATION_OPERATION_DELETE: {
+                return mSettingsRegistry.deleteSettingLocked(
+                        SettingsRegistry.SETTINGS_TYPE_GLOBAL,
+                        UserHandle.USER_OWNER, name);
+            }
+
+            case MUTATION_OPERATION_UPDATE: {
+                return mSettingsRegistry.updateSettingLocked(SettingsRegistry.SETTINGS_TYPE_GLOBAL,
+                        UserHandle.USER_OWNER, name, value, getCallingPackage());
+            }
+        }
+
+        return false;
+    }
+
+    private Cursor getAllSecureSettingsLocked(int userId, String[] projection) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "getAllSecureSettings(" + userId + ")");
+        }
+
+        // Resolve the userId on whose behalf the call is made.
+        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+
+        List<String> names = mSettingsRegistry.getSettingsNamesLocked(
+                SettingsRegistry.SETTINGS_TYPE_SECURE, callingUserId);
+
+        final int nameCount = names.size();
+
+        String[] normalizedProjection = normalizeProjection(projection);
+        MatrixCursor result = new MatrixCursor(normalizedProjection, nameCount);
+
+        for (int i = 0; i < nameCount; i++) {
+            String name = names.get(i);
+
+            // Determine the owning user as some profile settings are cloned from the parent.
+            final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+
+            // Special case for location (sigh).
+            if (isLocationProvidersAllowedRestricted(name, callingUserId, owningUserId)) {
+                return null;
+            }
+
+            Setting setting = mSettingsRegistry.getSettingLocked(
+                    SettingsRegistry.SETTINGS_TYPE_SECURE, owningUserId, name);
+            appendSettingToCursor(result, setting);
+        }
+
+        return result;
+    }
+
+    private Setting getSecureSettingLocked(String name, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")");
+        }
+
+        // Resolve the userId on whose behalf the call is made.
+        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+
+        // Determine the owning user as some profile settings are cloned from the parent.
+        final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+
+        // Special case for location (sigh).
+        if (isLocationProvidersAllowedRestricted(name, callingUserId, owningUserId)) {
+            return null;
+        }
+
+        // Get the value.
+        return mSettingsRegistry.getSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
+                owningUserId, name);
+    }
+
+    private boolean insertSecureSettingLocked(String name, String value, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "insertSecureSettingLocked(" + name + ", " + value + ", "
+                    + requestingUserId + ")");
+        }
+
+        return mutateSecureSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+    }
+
+    private boolean deleteSecureSettingLocked(String name, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "deleteSecureSettingLocked(" + name + ", " + requestingUserId + ")");
+        }
+
+        return mutateSecureSettingLocked(name, null, requestingUserId, MUTATION_OPERATION_DELETE);
+    }
+
+    private boolean updateSecureSettingLocked(String name, String value, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "updateSecureSettingLocked(" + name + ", " + value + ", "
+                    + requestingUserId + ")");
+        }
+
+        return mutateSecureSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
+    }
+
+    private boolean mutateSecureSettingLocked(String name, String value, int requestingUserId,
+            int operation) {
+        // Make sure the caller can change the settings.
+        enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
+
+        // Verify whether this operation is allowed for the calling package.
+        if (!isAppOpWriteSettingsAllowedForCallingPackage()) {
+            return false;
+        }
+
+        // Resolve the userId on whose behalf the call is made.
+        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+
+        // If this is a setting that is currently restricted for this user, done.
+        if (isGlobalOrSecureSettingRestrictedForUser(name, callingUserId)) {
+            return false;
+        }
+
+        // Determine the owning user as some profile settings are cloned from the parent.
+        final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+
+        // Only the owning user can change the setting.
+        if (owningUserId != callingUserId) {
+            return false;
+        }
+
+        // Special cases for location providers (sigh).
+        if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+            return updateLocationProvidersAllowed(value, owningUserId);
+        }
+
+        // Mutate the value.
+        switch(operation) {
+            case MUTATION_OPERATION_INSERT: {
+                return mSettingsRegistry.insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
+                        owningUserId, name, value, getCallingPackage());
+            }
+
+            case MUTATION_OPERATION_DELETE: {
+                return mSettingsRegistry.deleteSettingLocked(
+                        SettingsRegistry.SETTINGS_TYPE_SECURE,
+                        owningUserId, name);
+            }
+
+            case MUTATION_OPERATION_UPDATE: {
+                return mSettingsRegistry.updateSettingLocked(SettingsRegistry.SETTINGS_TYPE_SECURE,
+                        owningUserId, name, value, getCallingPackage());
+            }
+        }
+
+        return false;
+    }
+
+    private Cursor getAllSystemSettingsLocked(int userId, String[] projection) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "getAllSecureSystemLocked(" + userId + ")");
+        }
+
+        // Resolve the userId on whose behalf the call is made.
+        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+
+        List<String> names = mSettingsRegistry.getSettingsNamesLocked(
+                SettingsRegistry.SETTINGS_TYPE_SYSTEM, callingUserId);
+
+        final int nameCount = names.size();
+
+        String[] normalizedProjection = normalizeProjection(projection);
+        MatrixCursor result = new MatrixCursor(normalizedProjection, nameCount);
+
+        for (int i = 0; i < nameCount; i++) {
+            String name = names.get(i);
+
+            // Determine the owning user as some profile settings are cloned from the parent.
+            final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
+
+            Setting setting = mSettingsRegistry.getSettingLocked(
+                    SettingsRegistry.SETTINGS_TYPE_SYSTEM, owningUserId, name);
+            appendSettingToCursor(result, setting);
+        }
+
+        return result;
+    }
+
+    private Setting getSystemSettingLocked(String name, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "getSystemSetting(" + name + ", " + requestingUserId + ")");
+        }
+
+        // Resolve the userId on whose behalf the call is made.
+        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+
+        // Determine the owning user as some profile settings are cloned from the parent.
+        final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
+
+        // Get the value.
+        return mSettingsRegistry.getSettingLocked(SettingsRegistry.SETTINGS_TYPE_SYSTEM,
+                owningUserId, name);
+    }
+
+    private boolean insertSystemSettingLocked(String name, String value, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "insertSystemSettingLocked(" + name + ", " + value + ", "
+                    + requestingUserId + ")");
+        }
+
+        return mutateSystemSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+    }
+
+    private boolean deleteSystemSettingLocked(String name, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "deleteSystemSettingLocked(" + name + ", " + requestingUserId + ")");
+        }
+
+        return mutateSystemSettingLocked(name, null, requestingUserId, MUTATION_OPERATION_DELETE);
+    }
+
+    private boolean updateSystemSettingLocked(String name, String value, int requestingUserId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "updateSystemSettingLocked(" + name + ", " + value + ", "
+                    + requestingUserId + ")");
+        }
+
+        return mutateSystemSettingLocked(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
+    }
+
+    private boolean mutateSystemSettingLocked(String name, String value, int runAsUserId,
+            int operation) {
+        // Make sure the caller can change the settings.
+        enforceWritePermission(Manifest.permission.WRITE_SETTINGS);
+
+        // Verify whether this operation is allowed for the calling package.
+        if (!isAppOpWriteSettingsAllowedForCallingPackage()) {
+            return false;
+        }
+
+        // Enforce what the calling package can mutate in the system settings.
+        enforceRestrictedSystemSettingsMutationForCallingPackageLocked(operation, name);
+
+        // Resolve the userId on whose behalf the call is made.
+        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId);
+
+        // Determine the owning user as some profile settings are cloned from the parent.
+        final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
+
+        // Only the owning user id can change the setting.
+        if (owningUserId != callingUserId) {
+            return false;
+        }
+
+        // Mutate the value.
+        switch (operation) {
+            case MUTATION_OPERATION_INSERT: {
+                validateSystemSettingValue(name, value);
+                return mSettingsRegistry.insertSettingLocked(SettingsRegistry.SETTINGS_TYPE_SYSTEM,
+                        owningUserId, name, value, getCallingPackage());
+            }
+
+            case MUTATION_OPERATION_DELETE: {
+                return mSettingsRegistry.deleteSettingLocked(
+                        SettingsRegistry.SETTINGS_TYPE_SYSTEM,
+                        owningUserId, name);
+            }
+
+            case MUTATION_OPERATION_UPDATE: {
+                validateSystemSettingValue(name, value);
+                return mSettingsRegistry.updateSettingLocked(SettingsRegistry.SETTINGS_TYPE_SYSTEM,
+                        owningUserId, name, value, getCallingPackage());
+            }
+        }
+
+        return false;
+    }
+
+    private void validateSystemSettingValue(String name, String value) {
+        Settings.System.Validator validator = Settings.System.VALIDATORS.get(name);
+        if (validator != null && !validator.validate(value)) {
+            throw new IllegalArgumentException("Invalid value: " + value
+                    + " for setting: " + name);
+        }
+    }
+
+    private boolean isLocationProvidersAllowedRestricted(String name, int callingUserId,
+            int owningUserId) {
+        // Optimization - location providers are restricted only for managed profiles.
+        if (callingUserId == owningUserId) {
+            return false;
+        }
+        if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)
+                && mUserManager.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION,
+                new UserHandle(callingUserId))) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isGlobalOrSecureSettingRestrictedForUser(String setting, int userId) {
+        String restriction = sSettingToUserRestrictionMap.get(setting);
+        if (restriction == null) {
+            return false;
+        }
+        return mUserManager.hasUserRestriction(restriction, new UserHandle(userId));
+    }
+
+    private int resolveOwningUserIdForSecureSettingLocked(int userId, String setting) {
+        return resolveOwningUserIdLocked(userId, sSecureCloneToManagedSettings, setting);
+    }
+
+    private int resolveOwningUserIdForSystemSettingLocked(int userId, String setting) {
+        return resolveOwningUserIdLocked(userId, sSystemCloneToManagedSettings, setting);
+    }
+
+    private int resolveOwningUserIdLocked(int userId, Set<String> keys, String name) {
+        final int parentId = getGroupParentLocked(userId);
+        if (parentId != userId && keys.contains(name)) {
+            return parentId;
+        }
+        return userId;
+    }
+
+    private void enforceRestrictedSystemSettingsMutationForCallingPackageLocked(int operation,
+            String name) {
+        // System/root/shell can mutate whatever secure settings they want.
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid == android.os.Process.SYSTEM_UID
+                || callingUid == Process.SHELL_UID
+                || callingUid == Process.ROOT_UID) {
+            return;
+        }
+
+        switch (operation) {
+            case MUTATION_OPERATION_INSERT:
+                // Insert updates.
+            case MUTATION_OPERATION_UPDATE: {
+                if (Settings.System.PUBLIC_SETTINGS.contains(name)) {
+                    return;
+                }
+
+                // The calling package is already verified.
+                PackageInfo packageInfo = getCallingPackageInfoOrThrow();
+
+                // Privileged apps can do whatever they want.
+                if ((packageInfo.applicationInfo.privateFlags
+                        & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+                    return;
+                }
+
+                warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
+                        packageInfo.applicationInfo.targetSdkVersion, name);
+            } break;
+
+            case MUTATION_OPERATION_DELETE: {
+                if (Settings.System.PUBLIC_SETTINGS.contains(name)
+                        || Settings.System.PRIVATE_SETTINGS.contains(name)) {
+                    throw new IllegalArgumentException("You cannot delete system defined"
+                            + " secure settings.");
+                }
+
+                // The calling package is already verified.
+                PackageInfo packageInfo = getCallingPackageInfoOrThrow();
+
+                // Privileged apps can do whatever they want.
+                if ((packageInfo.applicationInfo.privateFlags &
+                        ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+                    return;
+                }
+
+                warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
+                        packageInfo.applicationInfo.targetSdkVersion, name);
+            } break;
+        }
+    }
+
+    private PackageInfo getCallingPackageInfoOrThrow() {
+        try {
+            return mPackageManager.getPackageInfo(getCallingPackage(), 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalStateException("Calling package doesn't exist");
+        }
+    }
+
+    private int getGroupParentLocked(int userId) {
+        // Most frequent use case.
+        if (userId == UserHandle.USER_OWNER) {
+            return userId;
+        }
+        // We are in the same process with the user manager and the returned
+        // user info is a cached instance, so just look up instead of cache.
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            UserInfo userInfo = mUserManager.getProfileParent(userId);
+            return (userInfo != null) ? userInfo.id : userId;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private boolean isAppOpWriteSettingsAllowedForCallingPackage() {
+        final int callingUid = Binder.getCallingUid();
+
+        mAppOpsManager.checkPackage(Binder.getCallingUid(), getCallingPackage());
+
+        return mAppOpsManager.noteOp(AppOpsManager.OP_WRITE_SETTINGS, callingUid,
+                getCallingPackage()) == AppOpsManager.MODE_ALLOWED;
+    }
+
+    private void enforceWritePermission(String permission) {
+        if (getContext().checkCallingOrSelfPermission(permission)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Permission denial: writing to settings requires:"
+                    + permission);
+        }
+    }
+
+    /*
+     * Used to parse changes to the value of Settings.Secure.LOCATION_PROVIDERS_ALLOWED.
+     * This setting contains a list of the currently enabled location providers.
+     * But helper functions in android.providers.Settings can enable or disable
+     * a single provider by using a "+" or "-" prefix before the provider name.
+     *
+     * @returns whether the enabled location providers changed.
      */
-    private static final class SettingsCache extends LruCache<String, Bundle> {
-
-        private final String mCacheName;
-        private boolean mCacheFullyMatchesDisk = false;  // has the whole database slurped.
-
-        public SettingsCache(String name) {
-            super(MAX_CACHE_ENTRIES);
-            mCacheName = name;
+    private boolean updateLocationProvidersAllowed(String value, int owningUserId) {
+        if (TextUtils.isEmpty(value)) {
+            return false;
         }
 
-        /**
-         * Is the whole database table slurped into this cache?
-         */
-        public boolean fullyMatchesDisk() {
-            synchronized (this) {
-                return mCacheFullyMatchesDisk;
+        final char prefix = value.charAt(0);
+        if (prefix != '+' && prefix != '-') {
+            return false;
+        }
+
+        // skip prefix
+        value = value.substring(1);
+
+        Setting settingValue = getSecureSettingLocked(
+                Settings.Secure.LOCATION_PROVIDERS_ALLOWED, owningUserId);
+
+        String oldProviders = (settingValue != null) ? settingValue.getValue() : "";
+
+        int index = oldProviders.indexOf(value);
+        int end = index + value.length();
+
+        // check for commas to avoid matching on partial string
+        if (index > 0 && oldProviders.charAt(index - 1) != ',') {
+            index = -1;
+        }
+
+        // check for commas to avoid matching on partial string
+        if (end < oldProviders.length() && oldProviders.charAt(end) != ',') {
+            index = -1;
+        }
+
+        String newProviders;
+
+        if (prefix == '+' && index < 0) {
+            // append the provider to the list if not present
+            if (oldProviders.length() == 0) {
+                newProviders = value;
+            } else {
+                newProviders = oldProviders + ',' + value;
+            }
+        } else if (prefix == '-' && index >= 0) {
+            // remove the provider from the list if present
+            // remove leading or trailing comma
+            if (index > 0) {
+                index--;
+            } else if (end < oldProviders.length()) {
+                end++;
+            }
+
+            newProviders = oldProviders.substring(0, index);
+            if (end < oldProviders.length()) {
+                newProviders += oldProviders.substring(end);
+            }
+        } else {
+            // nothing changed, so no need to update the database
+            return false;
+        }
+
+        updateSecureSettingLocked(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                newProviders, owningUserId);
+
+        return true;
+    }
+
+    private void sendNotify(Uri uri, int userId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            getContext().getContentResolver().notifyChange(uri, null, true, userId);
+            if (DEBUG) {
+                Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
+            int targetSdkVersion, String name) {
+        // If the app targets Lollipop MR1 or older SDK we warn, otherwise crash.
+        if (targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+            if (Settings.System.PRIVATE_SETTINGS.contains(name)) {
+                Slog.w(LOG_TAG, "You shouldn't not change private system settings."
+                        + " This will soon become an error.");
+            } else {
+                Slog.w(LOG_TAG, "You shouldn't keep your settings in the secure settings."
+                        + " This will soon become an error.");
+            }
+        } else {
+            if (Settings.System.PRIVATE_SETTINGS.contains(name)) {
+                throw new IllegalArgumentException("You cannot change private secure settings.");
+            } else {
+                throw new IllegalArgumentException("You cannot keep your settings in"
+                        + " the secure settings.");
+            }
+        }
+    }
+
+    private static int resolveCallingUserIdEnforcingPermissionsLocked(int requestingUserId) {
+        if (requestingUserId == UserHandle.getCallingUserId()) {
+            return requestingUserId;
+        }
+        return ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), requestingUserId, false, true,
+                "get/set setting for user", null);
+    }
+
+    private static Bundle packageValueForCallResult(Setting setting) {
+        if (setting == null) {
+            return NULL_SETTING;
+        }
+        return Bundle.forPair(Settings.NameValueTable.VALUE, setting.getValue());
+    }
+
+    private static int getRequestingUserId(Bundle args) {
+        final int callingUserId = UserHandle.getCallingUserId();
+        return (args != null) ? args.getInt(Settings.CALL_METHOD_USER_KEY, callingUserId)
+                : callingUserId;
+    }
+
+    private static String getSettingValue(Bundle args) {
+        return (args != null) ? args.getString(Settings.NameValueTable.VALUE) : null;
+    }
+
+    private static String getValidTableOrThrow(Uri uri) {
+        if (uri.getPathSegments().size() > 0) {
+            String table = uri.getPathSegments().get(0);
+            if (DatabaseHelper.isValidTable(table)) {
+                return table;
+            }
+            throw new IllegalArgumentException("Bad root path: " + table);
+        }
+        throw new IllegalArgumentException("Invalid URI:" + uri);
+    }
+
+    private static MatrixCursor packageSettingForQuery(Setting setting, String[] projection) {
+        if (setting == null) {
+            return new MatrixCursor(projection, 0);
+        }
+        MatrixCursor cursor = new MatrixCursor(projection, 1);
+        appendSettingToCursor(cursor, setting);
+        return cursor;
+    }
+
+    private static String[] normalizeProjection(String[] projection) {
+        if (projection == null) {
+            return ALL_COLUMNS;
+        }
+
+        final int columnCount = projection.length;
+        for (int i = 0; i < columnCount; i++) {
+            String column = projection[i];
+            if (!ArrayUtils.contains(ALL_COLUMNS, column)) {
+                throw new IllegalArgumentException("Invalid column: " + column);
             }
         }
 
-        public void setFullyMatchesDisk(boolean value) {
-            synchronized (this) {
-                mCacheFullyMatchesDisk = value;
+        return projection;
+    }
+
+    private static void appendSettingToCursor(MatrixCursor cursor, Setting setting) {
+        final int columnCount = cursor.getColumnCount();
+
+        String[] values =  new String[columnCount];
+
+        for (int i = 0; i < columnCount; i++) {
+            String column = cursor.getColumnName(i);
+
+            switch (column) {
+                case Settings.NameValueTable._ID: {
+                    values[i] = setting.getId();
+                } break;
+
+                case Settings.NameValueTable.NAME: {
+                    values[i] = setting.getName();
+                } break;
+
+                case Settings.NameValueTable.VALUE: {
+                    values[i] = setting.getValue();
+                } break;
             }
         }
 
-        @Override
-        protected void entryRemoved(boolean evicted, String key, Bundle oldValue, Bundle newValue) {
-            if (evicted) {
-                mCacheFullyMatchesDisk = false;
-            }
-        }
+        cursor.addRow(values);
+    }
 
-        /**
-         * Atomic cache population, conditional on size of value and if
-         * we lost a race.
-         *
-         * @returns a Bundle to send back to the client from call(), even
-         *     if we lost the race.
-         */
-        public Bundle putIfAbsent(String key, String value) {
-            Bundle bundle = (value == null) ? NULL_SETTING : Bundle.forPair("value", value);
-            if (value == null || value.length() <= MAX_CACHE_ENTRY_SIZE) {
-                synchronized (this) {
-                    if (get(key) == null) {
-                        put(key, bundle);
+    private static final class Arguments {
+        private static final Pattern WHERE_PATTERN_WITH_PARAM_NO_BRACKETS =
+                Pattern.compile("[\\s]*name[\\s]*=[\\s]*\\?[\\s]*");
+
+        private static final Pattern WHERE_PATTERN_WITH_PARAM_IN_BRACKETS =
+                Pattern.compile("[\\s]*\\([\\s]*name[\\s]*=[\\s]*\\?[\\s]*\\)[\\s]*");
+
+        private static final Pattern WHERE_PATTERN_NO_PARAM_IN_BRACKETS =
+                Pattern.compile("[\\s]*\\([\\s]*name[\\s]*=[\\s]*['\"].*['\"][\\s]*\\)[\\s]*");
+
+        private static final Pattern WHERE_PATTERN_NO_PARAM_NO_BRACKETS =
+                Pattern.compile("[\\s]*name[\\s]*=[\\s]*['\"].*['\"][\\s]*");
+
+        public final String table;
+        public final String name;
+
+        public Arguments(Uri uri, String where, String[] whereArgs, boolean supportAll) {
+            final int segmentSize = uri.getPathSegments().size();
+            switch (segmentSize) {
+                case 1: {
+                    if (where != null
+                            && (WHERE_PATTERN_WITH_PARAM_NO_BRACKETS.matcher(where).matches()
+                                || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches())
+                            && whereArgs.length == 1) {
+                        name = whereArgs[0];
+                        table = computeTableForSetting(uri, name);
+                    } else if (where != null
+                            && (WHERE_PATTERN_NO_PARAM_NO_BRACKETS.matcher(where).matches()
+                                || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) {
+                        final int startIndex = Math.max(where.indexOf("'"),
+                                where.indexOf("\"")) + 1;
+                        final int endIndex = Math.max(where.lastIndexOf("'"),
+                                where.lastIndexOf("\""));
+                        name = where.substring(startIndex, endIndex);
+                        table = computeTableForSetting(uri, name);
+                    } else if (supportAll && where == null && whereArgs == null) {
+                        name = null;
+                        table = computeTableForSetting(uri, null);
+                    } else if (uri.getPathSegments().size() == 2
+                            && where == null && whereArgs == null) {
+                        name = uri.getPathSegments().get(1);
+                        table = computeTableForSetting(uri, name);
+                    } else {
+                        EventLogTags.writeUnsupportedSettingsQuery(
+                                uri.toSafeString(), where, Arrays.toString(whereArgs));
+                        throw new IllegalArgumentException("Only null where and args"
+                                + " or name=? where and a single arg or name='SOME_SETTING' "
+                                + "are supported uri: " + uri + " where: " + where + " args: "
+                                + Arrays.toString(whereArgs));
                     }
+                } break;
+
+                default: {
+                    throw new IllegalArgumentException("Invalid URI: " + uri);
                 }
             }
-            return bundle;
         }
 
-        /**
-         * Populates a key in a given (possibly-null) cache.
-         */
-        public static void populate(SettingsCache cache, ContentValues contentValues) {
-            if (cache == null) {
-                return;
+        public static String computeTableForSetting(Uri uri, String name) {
+            String table = getValidTableOrThrow(uri);
+
+            if (name != null) {
+                if (sSystemMovedToSecureSettings.contains(name)) {
+                    table = TABLE_SECURE;
+                }
+
+                if (sSystemMovedToGlobalSettings.contains(name)) {
+                    table = TABLE_GLOBAL;
+                }
+
+                if (sSecureMovedToGlobalSettings.contains(name)) {
+                    table = TABLE_GLOBAL;
+                }
+
+                if (sGlobalMovedToSecureSettings.contains(name)) {
+                    table = TABLE_SECURE;
+                }
             }
-            String name = contentValues.getAsString(Settings.NameValueTable.NAME);
-            if (name == null) {
-                Log.w(TAG, "null name populating settings cache.");
-                return;
-            }
-            String value = contentValues.getAsString(Settings.NameValueTable.VALUE);
-            cache.populate(name, value);
+
+            return table;
+        }
+    }
+
+    final class SettingsRegistry {
+        private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
+
+        private static final int SETTINGS_TYPE_GLOBAL = 0;
+        private static final int SETTINGS_TYPE_SYSTEM = 1;
+        private static final int SETTINGS_TYPE_SECURE = 2;
+
+        private static final int SETTINGS_TYPE_MASK = 0xF0000000;
+        private static final int SETTINGS_TYPE_SHIFT = 28;
+
+        private static final String SETTINGS_FILE_GLOBAL = "settings_global.xml";
+        private static final String SETTINGS_FILE_SYSTEM = "settings_system.xml";
+        private static final String SETTINGS_FILE_SECURE = "settings_secure.xml";
+
+        private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>();
+
+        private final BackupManager mBackupManager;
+
+        public SettingsRegistry() {
+            mBackupManager = new BackupManager(getContext());
+            migrateAllLegacySettingsIfNeeded();
         }
 
-        public void populate(String name, String value) {
-            synchronized (this) {
-                if (value == null || value.length() <= MAX_CACHE_ENTRY_SIZE) {
-                    put(name, Bundle.forPair(Settings.NameValueTable.VALUE, value));
+        public List<String> getSettingsNamesLocked(int type, int userId) {
+            final int key = makeKey(type, userId);
+            SettingsState settingsState = peekSettingsStateLocked(key);
+            return settingsState.getSettingNamesLocked();
+        }
+
+        public SettingsState getSettingsLocked(int type, int userId) {
+            final int key = makeKey(type, userId);
+            return peekSettingsStateLocked(key);
+        }
+
+        public void ensureSettingsForUserLocked(int userId) {
+            // Migrate the setting for this user if needed.
+            migrateLegacySettingsForUserIfNeededLocked(userId);
+
+            // Ensure global settings loaded if owner.
+            if (userId == UserHandle.USER_OWNER) {
+                final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
+                ensureSettingsStateLocked(globalKey);
+            }
+
+            // Ensure secure settings loaded.
+            final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+            ensureSettingsStateLocked(secureKey);
+
+            // Make sure the secure settings have an Android id set.
+            SettingsState secureSettings = getSettingsLocked(SETTINGS_TYPE_SECURE, userId);
+            ensureSecureSettingAndroidIdSetLocked(secureSettings);
+
+            // Ensure system settings loaded.
+            final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
+            ensureSettingsStateLocked(systemKey);
+
+            // Upgrade the settings to the latest version.
+            UpgradeController upgrader = new UpgradeController(userId);
+            upgrader.upgradeIfNeededLocked();
+        }
+
+        private void ensureSettingsStateLocked(int key) {
+            if (mSettingsStates.get(key) == null) {
+                final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
+                SettingsState settingsState = new SettingsState(mLock, getSettingsFile(key), key,
+                        maxBytesPerPackage);
+                mSettingsStates.put(key, settingsState);
+            }
+        }
+
+        public void removeUserStateLocked(int userId, boolean permanently) {
+            // We always keep the global settings in memory.
+
+            // Nuke system settings.
+            final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
+            final SettingsState systemSettingsState = mSettingsStates.get(systemKey);
+            if (systemSettingsState != null) {
+                if (permanently) {
+                    mSettingsStates.remove(systemKey);
+                    systemSettingsState.destroyLocked(null);
                 } else {
-                    put(name, TOO_LARGE_TO_CACHE_MARKER);
+                    systemSettingsState.destroyLocked(new Runnable() {
+                        @Override
+                        public void run() {
+                            mSettingsStates.remove(systemKey);
+                        }
+                    });
+                }
+            }
+
+            // Nuke secure settings.
+            final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+            final SettingsState secureSettingsState = mSettingsStates.get(secureKey);
+            if (secureSettingsState != null) {
+                if (permanently) {
+                    mSettingsStates.remove(secureKey);
+                    secureSettingsState.destroyLocked(null);
+                } else {
+                    secureSettingsState.destroyLocked(new Runnable() {
+                        @Override
+                        public void run() {
+                            mSettingsStates.remove(secureKey);
+                        }
+                    });
                 }
             }
         }
 
-        /**
-         * For suppressing duplicate/redundant settings inserts early,
-         * checking our cache first (but without faulting it in),
-         * before going to sqlite with the mutation.
-         */
-        public static boolean isRedundantSetValue(SettingsCache cache, String name, String value) {
-            if (cache == null) return false;
-            synchronized (cache) {
-                Bundle bundle = cache.get(name);
-                if (bundle == null) return false;
-                String oldValue = bundle.getPairValue();
-                if (oldValue == null && value == null) return true;
-                if ((oldValue == null) != (value == null)) return false;
-                return oldValue.equals(value);
+        public boolean insertSettingLocked(int type, int userId, String name, String value,
+                String packageName) {
+            final int key = makeKey(type, userId);
+
+            SettingsState settingsState = peekSettingsStateLocked(key);
+            final boolean success = settingsState.insertSettingLocked(name, value, packageName);
+
+            if (success) {
+                notifyForSettingsChange(key, name);
+            }
+            return success;
+        }
+
+        public boolean deleteSettingLocked(int type, int userId, String name) {
+            final int key = makeKey(type, userId);
+
+            SettingsState settingsState = peekSettingsStateLocked(key);
+            final boolean success = settingsState.deleteSettingLocked(name);
+
+            if (success) {
+                notifyForSettingsChange(key, name);
+            }
+            return success;
+        }
+
+        public Setting getSettingLocked(int type, int userId, String name) {
+            final int key = makeKey(type, userId);
+
+            SettingsState settingsState = peekSettingsStateLocked(key);
+            return settingsState.getSettingLocked(name);
+        }
+
+        public boolean updateSettingLocked(int type, int userId, String name, String value,
+                String packageName) {
+            final int key = makeKey(type, userId);
+
+            SettingsState settingsState = peekSettingsStateLocked(key);
+            final boolean success = settingsState.updateSettingLocked(name, value, packageName);
+
+            if (success) {
+                notifyForSettingsChange(key, name);
+            }
+
+            return success;
+        }
+
+        public void onPackageRemovedLocked(String packageName, int userId) {
+            final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
+            SettingsState globalSettings = mSettingsStates.get(globalKey);
+            globalSettings.onPackageRemovedLocked(packageName);
+
+            final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+            SettingsState secureSettings = mSettingsStates.get(secureKey);
+            secureSettings.onPackageRemovedLocked(packageName);
+
+            final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
+            SettingsState systemSettings = mSettingsStates.get(systemKey);
+            systemSettings.onPackageRemovedLocked(packageName);
+        }
+
+        private SettingsState peekSettingsStateLocked(int key) {
+            SettingsState settingsState = mSettingsStates.get(key);
+            if (settingsState != null) {
+                return settingsState;
+            }
+
+            ensureSettingsForUserLocked(getUserIdFromKey(key));
+            return mSettingsStates.get(key);
+        }
+
+        private void migrateAllLegacySettingsIfNeeded() {
+            synchronized (mLock) {
+                final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
+                File globalFile = getSettingsFile(key);
+                if (globalFile.exists()) {
+                    return;
+                }
+
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    List<UserInfo> users = mUserManager.getUsers(true);
+
+                    final int userCount = users.size();
+                    for (int i = 0; i < userCount; i++) {
+                        final int userId = users.get(i).id;
+
+                        DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
+                        SQLiteDatabase database = dbHelper.getWritableDatabase();
+                        migrateLegacySettingsForUserLocked(dbHelper, database, userId);
+
+                        // Upgrade to the latest version.
+                        UpgradeController upgrader = new UpgradeController(userId);
+                        upgrader.upgradeIfNeededLocked();
+
+                        // Drop from memory if not a running user.
+                        if (!mUserManager.isUserRunning(new UserHandle(userId))) {
+                            removeUserStateLocked(userId, false);
+                        }
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        private void migrateLegacySettingsForUserIfNeededLocked(int userId) {
+            // Every user has secure settings and if no file we need to migrate.
+            final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+            File secureFile = getSettingsFile(secureKey);
+            if (secureFile.exists()) {
+                return;
+            }
+
+            DatabaseHelper dbHelper = new DatabaseHelper(getContext(), userId);
+            SQLiteDatabase database = dbHelper.getWritableDatabase();
+
+            migrateLegacySettingsForUserLocked(dbHelper, database, userId);
+        }
+
+        private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,
+                SQLiteDatabase database, int userId) {
+            // Move over the global settings if owner.
+            if (userId == UserHandle.USER_OWNER) {
+                final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, userId);
+                ensureSettingsStateLocked(globalKey);
+                SettingsState globalSettings = mSettingsStates.get(globalKey);
+                migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL);
+                globalSettings.persistSyncLocked();
+            }
+
+            // Move over the secure settings.
+            final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
+            ensureSettingsStateLocked(secureKey);
+            SettingsState secureSettings = mSettingsStates.get(secureKey);
+            migrateLegacySettingsLocked(secureSettings, database, TABLE_SECURE);
+            ensureSecureSettingAndroidIdSetLocked(secureSettings);
+            secureSettings.persistSyncLocked();
+
+            // Move over the system settings.
+            final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
+            ensureSettingsStateLocked(systemKey);
+            SettingsState systemSettings = mSettingsStates.get(systemKey);
+            migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM);
+            systemSettings.persistSyncLocked();
+
+            // Drop the database as now all is moved and persisted.
+            if (DROP_DATABASE_ON_MIGRATION) {
+                dbHelper.dropDatabase();
+            } else {
+                dbHelper.backupDatabase();
+            }
+        }
+
+        private void migrateLegacySettingsLocked(SettingsState settingsState,
+                SQLiteDatabase database, String table) {
+            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+            queryBuilder.setTables(table);
+
+            Cursor cursor = queryBuilder.query(database, ALL_COLUMNS,
+                    null, null, null, null, null);
+
+            if (cursor == null) {
+                return;
+            }
+
+            try {
+                if (!cursor.moveToFirst()) {
+                    return;
+                }
+
+                final int nameColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+                final int valueColumnIdx = cursor.getColumnIndex(Settings.NameValueTable.VALUE);
+
+                settingsState.setVersionLocked(database.getVersion());
+
+                while (!cursor.isAfterLast()) {
+                    String name = cursor.getString(nameColumnIdx);
+                    String value = cursor.getString(valueColumnIdx);
+                    settingsState.insertSettingLocked(name, value,
+                            SettingsState.SYSTEM_PACKAGE_NAME);
+                    cursor.moveToNext();
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+
+        private void ensureSecureSettingAndroidIdSetLocked(SettingsState secureSettings) {
+            Setting value = secureSettings.getSettingLocked(Settings.Secure.ANDROID_ID);
+
+            if (value != null) {
+                return;
+            }
+
+            final int userId = getUserIdFromKey(secureSettings.mKey);
+
+            final UserInfo user;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                user = mUserManager.getUserInfo(userId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            if (user == null) {
+                // Can happen due to races when deleting users - treat as benign.
+                return;
+            }
+
+            String androidId = Long.toHexString(new SecureRandom().nextLong());
+            secureSettings.insertSettingLocked(Settings.Secure.ANDROID_ID, androidId,
+                    SettingsState.SYSTEM_PACKAGE_NAME);
+
+            Slog.d(LOG_TAG, "Generated and saved new ANDROID_ID [" + androidId
+                    + "] for user " + userId);
+
+            // Write a drop box entry if it's a restricted profile
+            if (user.isRestricted()) {
+                DropBoxManager dbm = (DropBoxManager) getContext().getSystemService(
+                        Context.DROPBOX_SERVICE);
+                if (dbm != null && dbm.isTagEnabled(DROPBOX_TAG_USERLOG)) {
+                    dbm.addText(DROPBOX_TAG_USERLOG, System.currentTimeMillis()
+                            + "," + DROPBOX_TAG_USERLOG + "," + androidId + "\n");
+                }
+            }
+        }
+
+        private void notifyForSettingsChange(int key, String name) {
+            // Update the system property *first*, so if someone is listening for
+            // a notification and then using the contract class to get their data,
+            // the system property will be updated and they'll get the new data.
+
+            boolean backedUpDataChanged = false;
+            String property = null;
+            if (isGlobalSettingsKey(key)) {
+                property = Settings.Global.SYS_PROP_SETTING_VERSION;
+                backedUpDataChanged = true;
+            } else if (isSecureSettingsKey(key)) {
+                property = Settings.Secure.SYS_PROP_SETTING_VERSION;
+                backedUpDataChanged = true;
+            } else if (isSystemSettingsKey(key)) {
+                property = Settings.System.SYS_PROP_SETTING_VERSION;
+                backedUpDataChanged = true;
+            }
+
+            if (property != null) {
+                final long version = SystemProperties.getLong(property, 0) + 1;
+                SystemProperties.set(property, Long.toString(version));
+                if (DEBUG) {
+                    Slog.v(LOG_TAG, "System property " + property + "=" + version);
+                }
+            }
+
+            // Inform the backup manager about a data change
+            if (backedUpDataChanged) {
+                mBackupManager.dataChanged();
+            }
+
+            // Now send the notification through the content framework.
+
+            final int userId = getUserIdFromKey(key);
+            Uri uri = getNotificationUriFor(key, name);
+
+            sendNotify(uri, userId);
+        }
+
+        private int makeKey(int type, int userId) {
+            return (type << SETTINGS_TYPE_SHIFT) | userId;
+        }
+
+        private int getTypeFromKey(int key) {
+            return key >> SETTINGS_TYPE_SHIFT;
+        }
+
+        private int getUserIdFromKey(int key) {
+            return key & ~SETTINGS_TYPE_MASK;
+        }
+
+        private boolean isGlobalSettingsKey(int key) {
+            return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
+        }
+
+        private boolean isSystemSettingsKey(int key) {
+            return getTypeFromKey(key) == SETTINGS_TYPE_SYSTEM;
+        }
+
+        private boolean isSecureSettingsKey(int key) {
+            return getTypeFromKey(key) == SETTINGS_TYPE_SECURE;
+        }
+
+        private File getSettingsFile(int key) {
+            if (isGlobalSettingsKey(key)) {
+                final int userId = getUserIdFromKey(key);
+                return new File(Environment.getUserSystemDirectory(userId),
+                        SETTINGS_FILE_GLOBAL);
+            } else if (isSystemSettingsKey(key)) {
+                final int userId = getUserIdFromKey(key);
+                return new File(Environment.getUserSystemDirectory(userId),
+                        SETTINGS_FILE_SYSTEM);
+            } else if (isSecureSettingsKey(key)) {
+                final int userId = getUserIdFromKey(key);
+                return new File(Environment.getUserSystemDirectory(userId),
+                        SETTINGS_FILE_SECURE);
+            } else {
+                throw new IllegalArgumentException("Invalid settings key:" + key);
+            }
+        }
+
+        private Uri getNotificationUriFor(int key, String name) {
+            if (isGlobalSettingsKey(key)) {
+                return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
+                        : Settings.Global.CONTENT_URI;
+            } else if (isSecureSettingsKey(key)) {
+                return (name != null) ? Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name)
+                        : Settings.Secure.CONTENT_URI;
+            } else if (isSystemSettingsKey(key)) {
+                return (name != null) ? Uri.withAppendedPath(Settings.System.CONTENT_URI, name)
+                        : Settings.System.CONTENT_URI;
+            } else {
+                throw new IllegalArgumentException("Invalid settings key:" + key);
+            }
+        }
+
+        private int getMaxBytesPerPackageForType(int type) {
+            switch (type) {
+                case SETTINGS_TYPE_GLOBAL:
+                case SETTINGS_TYPE_SECURE: {
+                    return SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED;
+                }
+
+                default: {
+                    return SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED;
+                }
+            }
+        }
+
+        private final class UpgradeController {
+            private static final int SETTINGS_VERSION = 118;
+
+            private final int mUserId;
+
+            public UpgradeController(int userId) {
+                mUserId = userId;
+            }
+
+            public void upgradeIfNeededLocked() {
+                // The version of all settings for a user is the same (all users have secure).
+                SettingsState secureSettings = getSettingsLocked(
+                        SettingsRegistry.SETTINGS_TYPE_SECURE, mUserId);
+
+                // Try an update from the current state.
+                final int oldVersion = secureSettings.getVersionLocked();
+                final int newVersion = SETTINGS_VERSION;
+
+                // If up do data - done.
+                if (oldVersion == newVersion) {
+                    return;
+                }
+
+                // Try to upgrade.
+                final int curVersion = onUpgradeLocked(mUserId, oldVersion, newVersion);
+
+                // If upgrade failed start from scratch and upgrade.
+                if (curVersion != newVersion) {
+                    // Drop state we have for this user.
+                    removeUserStateLocked(mUserId, true);
+
+                    // Recreate the database.
+                    DatabaseHelper dbHelper = new DatabaseHelper(getContext(), mUserId);
+                    SQLiteDatabase database = dbHelper.getWritableDatabase();
+                    dbHelper.recreateDatabase(database, newVersion, curVersion, oldVersion);
+
+                    // Migrate the settings for this user.
+                    migrateLegacySettingsForUserLocked(dbHelper, database, mUserId);
+
+                    // Now upgrade should work fine.
+                    onUpgradeLocked(mUserId, oldVersion, newVersion);
+                }
+
+                // Set the global settings version if owner.
+                if (mUserId == UserHandle.USER_OWNER) {
+                    SettingsState globalSettings = getSettingsLocked(
+                            SettingsRegistry.SETTINGS_TYPE_GLOBAL, mUserId);
+                    globalSettings.setVersionLocked(newVersion);
+                }
+
+                // Set the secure settings version.
+                secureSettings.setVersionLocked(newVersion);
+
+                // Set the system settings version.
+                SettingsState systemSettings = getSettingsLocked(
+                        SettingsRegistry.SETTINGS_TYPE_SYSTEM, mUserId);
+                systemSettings.setVersionLocked(newVersion);
+            }
+
+            private SettingsState getGlobalSettingsLocked() {
+                return getSettingsLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_OWNER);
+            }
+
+            private SettingsState getSecureSettingsLocked(int userId) {
+                return getSettingsLocked(SETTINGS_TYPE_SECURE, userId);
+            }
+
+            private SettingsState getSystemSettingsLocked(int userId) {
+                return getSettingsLocked(SETTINGS_TYPE_SYSTEM, userId);
+            }
+
+            private int onUpgradeLocked(int userId, int oldVersion, int newVersion) {
+                if (DEBUG) {
+                    Slog.w(LOG_TAG, "Upgrading settings for user: " + userId + " from version: "
+                            + oldVersion + " to version: " + newVersion);
+                }
+
+                // You must perform all necessary mutations to bring the settings
+                // for this user from the old to the new version. When you add a new
+                // upgrade step you *must* update SETTINGS_VERSION.
+
+                /**
+                 * This is an example of moving a setting from secure to global.
+                 *
+                 * int currentVersion = oldVersion;
+                 * if (currentVersion == 118) {
+                 *     // Remove from the secure settings.
+                 *     SettingsState secureSettings = getSecureSettingsLocked(userId);
+                 *     String name = "example_setting_to_move";
+                 *     String value = secureSettings.getSetting(name);
+                 *     secureSettings.deleteSetting(name);
+                 *
+                 *     // Add to the global settings.
+                 *     SettingsState globalSettings = getGlobalSettingsLocked();
+                 *     globalSettings.insertSetting(name, value, SettingsState.SYSTEM_PACKAGE_NAME);
+                 *
+                 *     // Update the current version.
+                 *     currentVersion = 119;
+                 * }
+                 *
+                 * // Return the current version.
+                 * return currentVersion;
+                 */
+
+                return SettingsState.VERSION_UNDEFINED;
             }
         }
     }