Secure setting for LocationFudger's accuracy

This replaces the ACCURACY_METERS constant and all derived values with
a secure setting. This value defaults to 2km and has a hardcoded floor
of 500m.

Bug: 6982024
Change-Id: Ibf97ab57145abf28c4a9747444f40250adddf23c
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 37dae35..c26448f 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -210,7 +210,7 @@
 
         mBlacklist = new LocationBlacklist(mContext, mLocationHandler);
         mBlacklist.init();
-        mLocationFudger = new LocationFudger();
+        mLocationFudger = new LocationFudger(mContext, mLocationHandler);
 
         synchronized (mLock) {
             loadProvidersLocked();
diff --git a/services/java/com/android/server/location/LocationFudger.java b/services/java/com/android/server/location/LocationFudger.java
index 57bc1c5..84fd255 100644
--- a/services/java/com/android/server/location/LocationFudger.java
+++ b/services/java/com/android/server/location/LocationFudger.java
@@ -19,10 +19,14 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.security.SecureRandom;
+import android.content.Context;
+import android.database.ContentObserver;
 import android.location.Location;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.util.Log;
 
 
@@ -39,22 +43,19 @@
     private static final String EXTRA_COARSE_LOCATION = "coarseLocation";
 
     /**
-     * This is the main control: Best location accuracy allowed for coarse applications.
+     * Default coarse accuracy in meters.
      */
-    private static final float ACCURACY_METERS = 200.0f;
+    private static final float DEFAULT_ACCURACY_IN_METERS = 2000.0f;
 
     /**
-     * The distance between grids for snap-to-grid. See {@link #createCoarse}.
+     * Minimum coarse accuracy in meters.
      */
-    private static final double GRID_SIZE_METERS = ACCURACY_METERS;
+    private static final float MINIMUM_ACCURACY_IN_METERS = 200.0f;
 
     /**
-     * Standard deviation of the (normally distributed) random offset applied
-     * to coarse locations. It does not need to be as large as
-     * {@link #COARSE_ACCURACY_METERS} because snap-to-grid is the primary obfuscation
-     * method. See further details in the implementation.
+     * Secure settings key for coarse accuracy.
      */
-    private static final double STANDARD_DEVIATION_METERS = GRID_SIZE_METERS / 4.0;
+    private static final String COARSE_ACCURACY_CONFIG_NAME = "locationCoarseAccuracy";
 
     /**
      * This is the fastest interval that applications can receive coarse
@@ -106,43 +107,90 @@
     private final Object mLock = new Object();
     private final SecureRandom mRandom = new SecureRandom();
 
+    /**
+     * Used to monitor coarse accuracy secure setting for changes.
+     */
+    private final ContentObserver mSettingsObserver;
+
+    /**
+     * Used to resolve coarse accuracy setting.
+     */
+    private final Context mContext;
+
     // all fields below protected by mLock
     private double mOffsetLatitudeMeters;
     private double mOffsetLongitudeMeters;
     private long mNextInterval;
 
-    public LocationFudger() {
-        mOffsetLatitudeMeters = nextOffset();
-        mOffsetLongitudeMeters = nextOffset();
-        mNextInterval = SystemClock.elapsedRealtime() + CHANGE_INTERVAL_MS;
+    /**
+     * Best location accuracy allowed for coarse applications.
+     * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
+     */
+    private float mAccuracyInMeters;
+
+    /**
+     * The distance between grids for snap-to-grid. See {@link #createCoarse}.
+     * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
+     */
+    private double mGridSizeInMeters;
+
+    /**
+     * Standard deviation of the (normally distributed) random offset applied
+     * to coarse locations. It does not need to be as large as
+     * {@link #COARSE_ACCURACY_METERS} because snap-to-grid is the primary obfuscation
+     * method. See further details in the implementation.
+     * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
+     */
+    private double mStandardDeviationInMeters;
+
+    public LocationFudger(Context context, Handler handler) {
+        mContext = context;
+        mSettingsObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                setAccuracyInMeters(loadCoarseAccuracy());
+            }
+        };
+        mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+                COARSE_ACCURACY_CONFIG_NAME), false, mSettingsObserver);
+
+        float accuracy = loadCoarseAccuracy();
+        synchronized (mLock) {
+            setAccuracyInMetersLocked(accuracy);
+            mOffsetLatitudeMeters = nextOffsetLocked();
+            mOffsetLongitudeMeters = nextOffsetLocked();
+            mNextInterval = SystemClock.elapsedRealtime() + CHANGE_INTERVAL_MS;
+        }
     }
 
     /**
      * Get the cached coarse location, or generate a new one and cache it.
      */
     public Location getOrCreate(Location location) {
-        Bundle extras = location.getExtras();
-        if (extras == null) {
-            return addCoarseLocationExtra(location);
+        synchronized (mLock) {
+            Bundle extras = location.getExtras();
+            if (extras == null) {
+                return addCoarseLocationExtraLocked(location);
+            }
+            Parcelable parcel = extras.getParcelable(EXTRA_COARSE_LOCATION);
+            if (parcel == null) {
+                return addCoarseLocationExtraLocked(location);
+            }
+            if (!(parcel instanceof Location)) {
+                return addCoarseLocationExtraLocked(location);
+            }
+            Location coarse = (Location) parcel;
+            if (coarse.getAccuracy() < mAccuracyInMeters) {
+                return addCoarseLocationExtraLocked(location);
+            }
+            return coarse;
         }
-        Parcelable parcel = extras.getParcelable(EXTRA_COARSE_LOCATION);
-        if (parcel == null) {
-            return addCoarseLocationExtra(location);
-        }
-        if (!(parcel instanceof Location)) {
-            return addCoarseLocationExtra(location);
-        }
-        Location coarse = (Location) parcel;
-        if (coarse.getAccuracy() < ACCURACY_METERS) {
-            return addCoarseLocationExtra(location);
-        }
-        return coarse;
     }
 
-    private Location addCoarseLocationExtra(Location location) {
+    private Location addCoarseLocationExtraLocked(Location location) {
         Bundle extras = location.getExtras();
         if (extras == null) extras = new Bundle();
-        Location coarse = createCoarse(location);
+        Location coarse = createCoarseLocked(location);
         extras.putParcelable(EXTRA_COARSE_LOCATION, coarse);
         location.setExtras(extras);
         return coarse;
@@ -163,7 +211,7 @@
      * producing stable results, and mitigating against taking many samples
      * to average out a random offset.
      */
-    private Location createCoarse(Location fine) {
+    private Location createCoarseLocked(Location fine) {
         Location coarse = new Location(fine);
 
         // clean all the optional information off the location, because
@@ -188,14 +236,12 @@
         //
         // We apply the offset even if the location already claims to be
         // inaccurate, because it may be more accurate than claimed.
-        synchronized (mLock) {
-            updateRandomOffsetLocked();
-            // perform lon first whilst lat is still within bounds
-            lon += metersToDegreesLongitude(mOffsetLongitudeMeters, lat);
-            lat += metersToDegreesLatitude(mOffsetLatitudeMeters);
-            if (D) Log.d(TAG, String.format("applied offset of %.0f, %.0f (meters)",
-                    mOffsetLongitudeMeters, mOffsetLatitudeMeters));
-        }
+        updateRandomOffsetLocked();
+        // perform lon first whilst lat is still within bounds
+        lon += metersToDegreesLongitude(mOffsetLongitudeMeters, lat);
+        lat += metersToDegreesLatitude(mOffsetLatitudeMeters);
+        if (D) Log.d(TAG, String.format("applied offset of %.0f, %.0f (meters)",
+                mOffsetLongitudeMeters, mOffsetLatitudeMeters));
 
         // wrap
         lat = wrapLatitude(lat);
@@ -211,9 +257,9 @@
         // Note we quantize the latitude first, since the longitude
         // quantization depends on the latitude value and so leaks information
         // about the latitude
-        double latGranularity = metersToDegreesLatitude(GRID_SIZE_METERS);
+        double latGranularity = metersToDegreesLatitude(mGridSizeInMeters);
         lat = Math.round(lat / latGranularity) * latGranularity;
-        double lonGranularity = metersToDegreesLongitude(GRID_SIZE_METERS, lat);
+        double lonGranularity = metersToDegreesLongitude(mGridSizeInMeters, lat);
         lon = Math.round(lon / lonGranularity) * lonGranularity;
 
         // wrap again
@@ -223,7 +269,7 @@
         // apply
         coarse.setLatitude(lat);
         coarse.setLongitude(lon);
-        coarse.setAccuracy(Math.max(ACCURACY_METERS, coarse.getAccuracy()));
+        coarse.setAccuracy(Math.max(mAccuracyInMeters, coarse.getAccuracy()));
 
         if (D) Log.d(TAG, "fudged " + fine + " to " + coarse);
         return coarse;
@@ -259,16 +305,16 @@
         mNextInterval = now + CHANGE_INTERVAL_MS;
 
         mOffsetLatitudeMeters *= PREVIOUS_WEIGHT;
-        mOffsetLatitudeMeters += NEW_WEIGHT * nextOffset();
+        mOffsetLatitudeMeters += NEW_WEIGHT * nextOffsetLocked();
         mOffsetLongitudeMeters *= PREVIOUS_WEIGHT;
-        mOffsetLongitudeMeters += NEW_WEIGHT * nextOffset();
+        mOffsetLongitudeMeters += NEW_WEIGHT * nextOffsetLocked();
 
         if (D) Log.d(TAG, String.format("new offset: %.0f, %.0f (meters)",
                 mOffsetLongitudeMeters, mOffsetLatitudeMeters));
     }
 
-    private double nextOffset() {
-        return mRandom.nextGaussian() * STANDARD_DEVIATION_METERS;
+    private double nextOffsetLocked() {
+        return mRandom.nextGaussian() * mStandardDeviationInMeters;
     }
 
     private static double wrapLatitude(double lat) {
@@ -307,4 +353,45 @@
         pw.println(String.format("offset: %.0f, %.0f (meters)", mOffsetLongitudeMeters,
                 mOffsetLatitudeMeters));
     }
+
+    /**
+     * This is the main control: call this to set the best location accuracy
+     * allowed for coarse applications and all derived values.
+     */
+    private void setAccuracyInMetersLocked(float accuracyInMeters) {
+        mAccuracyInMeters = Math.max(accuracyInMeters, MINIMUM_ACCURACY_IN_METERS);
+        if (D) {
+            Log.d(TAG, "setAccuracyInMetersLocked: new accuracy = " + mAccuracyInMeters);
+        }
+        mGridSizeInMeters = mAccuracyInMeters;
+        mStandardDeviationInMeters = mGridSizeInMeters / 4.0;
+    }
+
+    /**
+     * Same as setAccuracyInMetersLocked without the pre-lock requirement.
+     */
+    private void setAccuracyInMeters(float accuracyInMeters) {
+        synchronized (mLock) {
+            setAccuracyInMetersLocked(accuracyInMeters);
+        }
+    }
+
+    /**
+     * Loads the coarse accuracy value from secure settings.
+     */
+    private float loadCoarseAccuracy() {
+        String newSetting = Settings.Secure.getString(mContext.getContentResolver(),
+                COARSE_ACCURACY_CONFIG_NAME);
+        if (D) {
+            Log.d(TAG, "loadCoarseAccuracy: newSetting = \"" + newSetting + "\"");
+        }
+        if (newSetting == null) {
+            return DEFAULT_ACCURACY_IN_METERS;
+        }
+        try {
+            return Float.parseFloat(newSetting);
+        } catch (NumberFormatException e) {
+            return DEFAULT_ACCURACY_IN_METERS;
+        }
+    }
 }