Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d335f9a9 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick Committed by Android Git Automerger
Browse files

am bb0eb5ca: Merge "Negatively cache settings and proactively slurp settings...

am bb0eb5ca: Merge "Negatively cache settings and proactively slurp settings into cache." into gingerbread

Merge commit 'bb0eb5ca' into gingerbread-plus-aosp

* commit 'bb0eb5ca':
  Negatively cache settings and proactively slurp settings into cache.
parents bc4389ed bb0eb5ca
Loading
Loading
Loading
Loading
+170 −12
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import android.app.backup.BackupManager;
import android.content.ContentProvider;
@@ -37,6 +39,7 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.ParcelFileDescriptor;
import android.os.SystemProperties;
import android.provider.DrmStore;
@@ -56,9 +59,15 @@ public class SettingsProvider extends ContentProvider {

    // Cache for settings, access-ordered for acting as LRU.
    // Guarded by themselves.
    private static final int MAX_CACHE_ENTRIES = 50;
    private static final SettingsCache sSystemCache = new SettingsCache();
    private static final SettingsCache sSecureCache = new SettingsCache();
    private static final int MAX_CACHE_ENTRIES = 200;
    private static final SettingsCache sSystemCache = new SettingsCache("system");
    private static final SettingsCache sSecureCache = new SettingsCache("secure");

    // The count of how many known (handled by SettingsProvider)
    // database mutations are currently being handled.  Used by
    // sFileObserver to not reload the database when it's ourselves
    // modifying it.
    private static final AtomicInteger sKnownMutationsInFlight = new AtomicInteger(0);

    // Over this size we don't reject loading or saving settings but
    // we do consider them broken/malicious and don't keep them in
@@ -67,6 +76,10 @@ public class SettingsProvider extends ContentProvider {

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

    protected DatabaseHelper mOpenHelper;
    private BackupManager mBackupManager;

@@ -201,6 +214,43 @@ public class SettingsProvider extends ContentProvider {
        }
    }

    // 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 SettingsFileObserver sObserverInstance;
    private class SettingsFileObserver extends FileObserver {
        private final AtomicBoolean mIsDirty = new AtomicBoolean(false);
        private final String mPath;

        public SettingsFileObserver(String path) {
            super(path, FileObserver.CLOSE_WRITE |
                  FileObserver.CREATE | FileObserver.DELETE |
                  FileObserver.MOVED_TO | FileObserver.MODIFY);
            mPath = path;
        }

        public void onEvent(int event, String path) {
            int modsInFlight = sKnownMutationsInFlight.get();
            if (modsInFlight > 0) {
                // our own modification.
                return;
            }
            Log.d(TAG, "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, "updating our caches for " + mPath);
            fullyPopulateCaches();
            mIsDirty.set(false);
        }
    }

    @Override
    public boolean onCreate() {
        mOpenHelper = new DatabaseHelper(getContext());
@@ -210,9 +260,65 @@ public class SettingsProvider extends ContentProvider {
            return false;
        }

        // Watch for external modifications to the database file,
        // keeping our cache in sync.
        // It's kinda lame to call mOpenHelper.getReadableDatabase()
        // during onCreate(), but since ensureAndroidIdIsSet has
        // already done it above and initialized/upgraded the
        // database, might as well just use it...
        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        sObserverInstance = new SettingsFileObserver(db.getPath());
        sObserverInstance.startWatching();
        startAsyncCachePopulation();
        return true;
    }

    private void startAsyncCachePopulation() {
        new Thread("populate-settings-caches") {
            public void run() {
                fullyPopulateCaches();
            }
        }.start();
    }

    private void fullyPopulateCaches() {
        fullyPopulateCache("secure", sSecureCache);
        fullyPopulateCache("system", sSystemCache);
    }

    // Slurp all values (if sane in number & size) into cache.
    private void fullyPopulateCache(String table, SettingsCache cache) {
        SQLiteDatabase db = mOpenHelper.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.clear();
                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);
                }
                Log.d(TAG, "cache for settings table '" + table + "' fullycached=" +
                      cache.fullyMatchesDisk());
            }
        } finally {
            c.close();
        }
    }

    private boolean ensureAndroidIdIsSet() {
        final Cursor c = query(Settings.Secure.CONTENT_URI,
                new String[] { Settings.NameValueTable.VALUE },
@@ -262,7 +368,19 @@ public class SettingsProvider extends ContentProvider {
    private Bundle lookupValue(String table, SettingsCache cache, String key) {
        synchronized (cache) {
            if (cache.containsKey(key)) {
                return cache.get(key);
                Bundle value = cache.get(key);
                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;
            }
        }

@@ -338,6 +456,7 @@ public class SettingsProvider extends ContentProvider {
        checkWritePermissions(args);
        SettingsCache cache = SettingsCache.forTable(args.table);

        sKnownMutationsInFlight.incrementAndGet();
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        db.beginTransaction();
        try {
@@ -350,6 +469,7 @@ public class SettingsProvider extends ContentProvider {
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
            sKnownMutationsInFlight.decrementAndGet();
        }

        sendNotify(uri);
@@ -449,8 +569,10 @@ public class SettingsProvider extends ContentProvider {
            return Uri.withAppendedPath(url, name);
        }

        sKnownMutationsInFlight.incrementAndGet();
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final long rowId = db.insert(args.table, null, initialValues);
        sKnownMutationsInFlight.decrementAndGet();
        if (rowId <= 0) return null;

        SettingsCache.populate(cache, initialValues);  // before we notify
@@ -471,12 +593,15 @@ public class SettingsProvider extends ContentProvider {
        }
        checkWritePermissions(args);

        sKnownMutationsInFlight.incrementAndGet();
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int count = db.delete(args.table, args.where, args.args);
        sKnownMutationsInFlight.decrementAndGet();
        if (count > 0) {
            SettingsCache.wipe(args.table);  // before we notify
            sendNotify(url);
        }
        startAsyncCachePopulation();
        if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) deleted");
        return count;
    }
@@ -489,12 +614,15 @@ public class SettingsProvider extends ContentProvider {
        }
        checkWritePermissions(args);

        sKnownMutationsInFlight.incrementAndGet();
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        sKnownMutationsInFlight.decrementAndGet();
        int count = db.update(args.table, initialValues, args.where, args.args);
        if (count > 0) {
            SettingsCache.wipe(args.table);  // before we notify
            sendNotify(url);
        }
        startAsyncCachePopulation();
        if (LOCAL_LOGV) Log.v(TAG, args.table + ": " + count + " row(s) <- " + initialValues);
        return count;
    }
@@ -607,13 +735,38 @@ public class SettingsProvider extends ContentProvider {
     */
    private static final class SettingsCache extends LinkedHashMap<String, Bundle> {

        public SettingsCache() {
        private final String mCacheName;
        private boolean mCacheFullyMatchesDisk = false;  // has the whole database slurped.

        public SettingsCache(String name) {
            super(MAX_CACHE_ENTRIES, 0.75f /* load factor */, true /* access ordered */);
            mCacheName = name;
        }

        /**
         * Is the whole database table slurped into this cache?
         */
        public boolean fullyMatchesDisk() {
            synchronized (this) {
                return mCacheFullyMatchesDisk;
            }
        }

        public void setFullyMatchesDisk(boolean value) {
            synchronized (this) {
                mCacheFullyMatchesDisk = value;
            }
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry eldest) {
            return size() > MAX_CACHE_ENTRIES;
            if (size() <= MAX_CACHE_ENTRIES) {
                return false;
            }
            synchronized (this) {
                mCacheFullyMatchesDisk = false;
            }
            return true;
        }

        /**
@@ -658,11 +811,15 @@ public class SettingsProvider extends ContentProvider {
                return;
            }
            String value = contentValues.getAsString(Settings.NameValueTable.VALUE);
            synchronized (cache) {
            cache.populate(name, value);
        }

        public void populate(String name, String value) {
            synchronized (this) {
                if (value == null || value.length() <= MAX_CACHE_ENTRY_SIZE) {
                    cache.put(name, Bundle.forPair(Settings.NameValueTable.VALUE, value));
                    put(name, Bundle.forPair(Settings.NameValueTable.VALUE, value));
                } else {
                    cache.remove(name);
                    put(name, TOO_LARGE_TO_CACHE_MARKER);
                }
            }
        }
@@ -678,6 +835,7 @@ public class SettingsProvider extends ContentProvider {
            }
            synchronized (cache) {
                cache.clear();
                cache.mCacheFullyMatchesDisk = true;
            }
        }