FileDocCategorySizeDatePackage
SettingsProvider.javaAPI DocAndroid 5.1 API57554Thu Mar 12 22:22:42 GMT 2015com.android.providers.settings

SettingsProvider

public class SettingsProvider extends android.content.ContentProvider

Fields Summary
private static final String
TAG
private static final boolean
LOCAL_LOGV
private static final boolean
USER_CHECK_THROWS
private static final String
TABLE_SYSTEM
private static final String
TABLE_SECURE
private static final String
TABLE_GLOBAL
private static final String
TABLE_FAVORITES
private static final String
TABLE_OLD_FAVORITES
private static final String[]
COLUMN_VALUE
private static final int
MAX_CACHE_ENTRIES
private static final android.util.SparseArray
sSystemCaches
private static final android.util.SparseArray
sSecureCaches
private static final SettingsCache
sGlobalCache
private static final android.util.SparseArray
sKnownMutationsInFlight
protected final android.util.SparseArray
mOpenHelpers
private List
mManagedProfiles
private static final int
MAX_CACHE_ENTRY_SIZE
private static final android.os.Bundle
NULL_SETTING
private static final android.os.Bundle
TOO_LARGE_TO_CACHE_MARKER
private android.os.UserManager
mUserManager
private android.app.backup.BackupManager
mBackupManager
static final HashSet
sSecureGlobalKeys
Settings which need to be treated as global/shared in multi-user environments.
static final HashSet
sSystemGlobalKeys
static final Map
sRestrictedKeys
private static final String
DROPBOX_TAG_USERLOG
static final HashSet
sSecureCloneToManagedKeys
static final HashSet
sSystemCloneToManagedKeys
private static android.util.SparseArray
sObserverInstances
Constructors Summary
Methods Summary
public intbulkInsert(android.net.Uri uri, android.content.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();
            }
        }

        sendNotify(uri, callingUser);
        return values.length;
    
public com.android.providers.settings.SettingsProvider$SettingsCachecacheForTable(int callingUser, java.lang.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;
        }
        return null;
    
public android.os.Bundlecall(java.lang.String method, java.lang.String request, android.os.Bundle args)
Fast path that avoids the use of chatty remoted Cursors.

        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;
    
private voidcheckUserRestrictions(java.lang.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.");
        }
    
private voidcheckWritePermissions(com.android.providers.settings.SettingsProvider$SqlArguments args)
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.

        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));
        }
    
public intdelete(android.net.Uri url, java.lang.String where, java.lang.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;
        }
        checkWritePermissions(args);

        final AtomicInteger mutationCount;
        synchronized (this) {
            mutationCount = sKnownMutationsInFlight.get(callingUser);
        }
        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;
    
private booleanensureAndroidIdIsSet(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;
                }

                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;
                }
                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");
                    }
                }
            }
            return true;
        } finally {
            c.close();
        }
    
private voidestablishDbTracking(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);
    
private voidfullyPopulateCache(DatabaseHelper dbHelper, java.lang.String table, com.android.providers.settings.SettingsProvider$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 voidfullyPopulateCaches(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));
    
private com.android.providers.settings.SettingsProvider$SettingsCachegetOrConstructCache(int callingUser, android.util.SparseArray which)

        getOrEstablishDatabase(callingUser); // ignore return value; we don't need it
        return which.get(callingUser);
    
private DatabaseHelpergetOrEstablishDatabase(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);
            }
        }

        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);
        }
    
public java.lang.StringgetType(android.net.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)) {
            return "vnd.android.cursor.dir/" + args.table;
        } else {
            return "vnd.android.cursor.item/" + args.table;
        }
    
private android.net.UrigetUriFor(android.net.Uri tableUri, android.content.ContentValues values, long rowId)
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

        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);
        }
    
public android.net.Uriinsert(android.net.Uri url, android.content.ContentValues initialValues)

        return insertForUser(url, initialValues, UserHandle.getCallingUserId());
    
private android.net.UriinsertForUser(android.net.Uri url, android.content.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");
        }

        if (LOCAL_LOGV) Slog.v(TAG, "insert(" + url + ") for user " + desiredUserHandle
                + " by " + callingUser);

        SqlArguments args = new SqlArguments(url);
        if (TABLE_FAVORITES.equals(args.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;
        }

        // 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);
                }
                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;
    
public voidinvalidateCache(int callingUser, java.lang.String tableName)
Used for wiping a whole cache on deletes when we're not sure what exactly was deleted or changed.

        SettingsCache cache = cacheForTable(callingUser, tableName);
        if (cache == null) {
            return;
        }
        synchronized (cache) {
            cache.evictAll();
            cache.mCacheFullyMatchesDisk = false;
        }
    
private booleanisManagedProfile(int callingUser)
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

        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;
        }
    
private android.os.BundlelookupValue(DatabaseHelper dbHelper, java.lang.String table, com.android.providers.settings.SettingsProvider$SettingsCache cache, java.lang.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;
    
public booleanonCreate()

        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();

        return true;
    
voidonProfilesChanged()
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.

        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);
            }
        }
    
voidonUserRemoved(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();
        }
    
public android.os.ParcelFileDescriptoropenFile(android.net.Uri uri, java.lang.String mode)

        throw new FileNotFoundException("Direct file access no longer supported; "
                + "ringtone playback is available through android.media.Ringtone");
    
private booleanparseProviderList(android.net.Uri url, android.content.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);

                // 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;
                    } else {
                        newProviders = providers + '," + 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 < 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);
                }
            }
        }

        return true;
    
public android.database.Cursorquery(android.net.Uri url, java.lang.String[] select, java.lang.String where, java.lang.String[] whereArgs, java.lang.String sort)

        return queryForUser(url, select, where, whereArgs, sort, UserHandle.getCallingUserId());
    
private android.database.CursorqueryForUser(android.net.Uri url, java.lang.String[] select, java.lang.String where, java.lang.String[] whereArgs, java.lang.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;
    
private voidsendNotify(android.net.Uri uri, int userHandle)
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

        // 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);
        }
    
private booleansettingMovedToGlobal(java.lang.String name)


     
        // 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);

        // 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);

        sRestrictedKeys = new HashMap<String, String>();
        sRestrictedKeys.put(Settings.Secure.LOCATION_MODE, UserManager.DISALLOW_SHARE_LOCATION);
        sRestrictedKeys.put(Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                UserManager.DISALLOW_SHARE_LOCATION);
        sRestrictedKeys.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,
                UserManager.ENSURE_VERIFY_APPS);
        sRestrictedKeys.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]);
        }
    
        return sSecureGlobalKeys.contains(name) || sSystemGlobalKeys.contains(name);
    
private booleanshouldShadowParentProfile(int userId, java.util.HashSet keys, java.lang.String name)
Check if the user is a managed profile and name is one of the settings to be cloned from the parent profile.

        return isManagedProfile(userId) && keys.contains(name);
    
private voidstartAsyncCachePopulation(int userHandle)

        new CachePrefetchThread(userHandle).start();
    
public intupdate(android.net.Uri url, android.content.ContentValues initialValues, java.lang.String where, java.lang.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;
        }
        checkWritePermissions(args);
        checkUserRestrictions(initialValues.getAsString(Settings.Secure.NAME), callingUser);

        final AtomicInteger mutationCount;
        synchronized (this) {
            mutationCount = sKnownMutationsInFlight.get(callingUser);
        }
        if (mutationCount != null) {
            mutationCount.incrementAndGet();
        }
        DatabaseHelper dbH = getOrEstablishDatabase(callingUser);
        SQLiteDatabase db = dbH.getWritableDatabase();
        int count = db.update(args.table, initialValues, 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) <- " + initialValues);
        return count;