blob: 2a68743c578d3937b9fb69aeb31ac2979bbf1192 [file] [log] [blame]
Nick Pelly74fa7ea2012-08-13 19:36:38 -07001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.location;
18
19import java.io.FileDescriptor;
20import java.io.PrintWriter;
21import java.security.SecureRandom;
Victoria Leasedf9ec612012-09-11 15:16:25 -070022import android.content.Context;
23import android.database.ContentObserver;
Nick Pelly74fa7ea2012-08-13 19:36:38 -070024import android.location.Location;
Victoria Lease09016ab2012-09-16 12:33:15 -070025import android.location.LocationManager;
Nick Pelly74fa7ea2012-08-13 19:36:38 -070026import android.os.Bundle;
Victoria Leasedf9ec612012-09-11 15:16:25 -070027import android.os.Handler;
Nick Pelly74fa7ea2012-08-13 19:36:38 -070028import android.os.Parcelable;
29import android.os.SystemClock;
Victoria Leasedf9ec612012-09-11 15:16:25 -070030import android.provider.Settings;
Nick Pelly74fa7ea2012-08-13 19:36:38 -070031import android.util.Log;
32
33
34/**
35 * Contains the logic to obfuscate (fudge) locations for coarse applications.
36 *
37 * <p>The goal is just to prevent applications with only
38 * the coarse location permission from receiving a fine location.
39 */
40public class LocationFudger {
41 private static final boolean D = false;
42 private static final String TAG = "LocationFudge";
43
Nick Pelly74fa7ea2012-08-13 19:36:38 -070044 /**
Victoria Leasedf9ec612012-09-11 15:16:25 -070045 * Default coarse accuracy in meters.
Nick Pelly74fa7ea2012-08-13 19:36:38 -070046 */
Victoria Leasedf9ec612012-09-11 15:16:25 -070047 private static final float DEFAULT_ACCURACY_IN_METERS = 2000.0f;
Nick Pelly74fa7ea2012-08-13 19:36:38 -070048
49 /**
Victoria Leasedf9ec612012-09-11 15:16:25 -070050 * Minimum coarse accuracy in meters.
Nick Pelly74fa7ea2012-08-13 19:36:38 -070051 */
Victoria Leasedf9ec612012-09-11 15:16:25 -070052 private static final float MINIMUM_ACCURACY_IN_METERS = 200.0f;
Nick Pelly74fa7ea2012-08-13 19:36:38 -070053
54 /**
Victoria Leasedf9ec612012-09-11 15:16:25 -070055 * Secure settings key for coarse accuracy.
Nick Pelly74fa7ea2012-08-13 19:36:38 -070056 */
Victoria Leasedf9ec612012-09-11 15:16:25 -070057 private static final String COARSE_ACCURACY_CONFIG_NAME = "locationCoarseAccuracy";
Nick Pelly74fa7ea2012-08-13 19:36:38 -070058
59 /**
60 * This is the fastest interval that applications can receive coarse
61 * locations.
62 */
63 public static final long FASTEST_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
64
65 /**
66 * The duration until we change the random offset.
67 */
68 private static final long CHANGE_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
69
70 /**
71 * The percentage that we change the random offset at every interval.
72 *
73 * <p>0.0 indicates the random offset doesn't change. 1.0
74 * indicates the random offset is completely replaced every interval.
75 */
76 private static final double CHANGE_PER_INTERVAL = 0.03; // 3% change
77
78 // Pre-calculated weights used to move the random offset.
79 //
80 // The goal is to iterate on the previous offset, but keep
81 // the resulting standard deviation the same. The variance of
82 // two gaussian distributions summed together is equal to the
83 // sum of the variance of each distribution. So some quick
84 // algebra results in the following sqrt calculation to
85 // weigh in a new offset while keeping the final standard
86 // deviation unchanged.
87 private static final double NEW_WEIGHT = CHANGE_PER_INTERVAL;
88 private static final double PREVIOUS_WEIGHT = Math.sqrt(1 - NEW_WEIGHT * NEW_WEIGHT);
89
90 /**
91 * This number actually varies because the earth is not round, but
92 * 111,000 meters is considered generally acceptable.
93 */
94 private static final int APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR = 111000;
95
96 /**
97 * Maximum latitude.
98 *
99 * <p>We pick a value 1 meter away from 90.0 degrees in order
100 * to keep cosine(MAX_LATITUDE) to a non-zero value, so that we avoid
101 * divide by zero fails.
102 */
103 private static final double MAX_LATITUDE = 90.0 -
104 (1.0 / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR);
105
106 private final Object mLock = new Object();
107 private final SecureRandom mRandom = new SecureRandom();
108
Victoria Leasedf9ec612012-09-11 15:16:25 -0700109 /**
110 * Used to monitor coarse accuracy secure setting for changes.
111 */
112 private final ContentObserver mSettingsObserver;
113
114 /**
115 * Used to resolve coarse accuracy setting.
116 */
117 private final Context mContext;
118
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700119 // all fields below protected by mLock
120 private double mOffsetLatitudeMeters;
121 private double mOffsetLongitudeMeters;
122 private long mNextInterval;
123
Victoria Leasedf9ec612012-09-11 15:16:25 -0700124 /**
125 * Best location accuracy allowed for coarse applications.
126 * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
127 */
128 private float mAccuracyInMeters;
129
130 /**
131 * The distance between grids for snap-to-grid. See {@link #createCoarse}.
132 * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
133 */
134 private double mGridSizeInMeters;
135
136 /**
137 * Standard deviation of the (normally distributed) random offset applied
138 * to coarse locations. It does not need to be as large as
139 * {@link #COARSE_ACCURACY_METERS} because snap-to-grid is the primary obfuscation
140 * method. See further details in the implementation.
141 * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
142 */
143 private double mStandardDeviationInMeters;
144
145 public LocationFudger(Context context, Handler handler) {
146 mContext = context;
147 mSettingsObserver = new ContentObserver(handler) {
148 @Override
149 public void onChange(boolean selfChange) {
150 setAccuracyInMeters(loadCoarseAccuracy());
151 }
152 };
153 mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
154 COARSE_ACCURACY_CONFIG_NAME), false, mSettingsObserver);
155
156 float accuracy = loadCoarseAccuracy();
157 synchronized (mLock) {
158 setAccuracyInMetersLocked(accuracy);
159 mOffsetLatitudeMeters = nextOffsetLocked();
160 mOffsetLongitudeMeters = nextOffsetLocked();
161 mNextInterval = SystemClock.elapsedRealtime() + CHANGE_INTERVAL_MS;
162 }
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700163 }
164
165 /**
166 * Get the cached coarse location, or generate a new one and cache it.
167 */
168 public Location getOrCreate(Location location) {
Victoria Leasedf9ec612012-09-11 15:16:25 -0700169 synchronized (mLock) {
Victoria Lease09016ab2012-09-16 12:33:15 -0700170 Location coarse = location.getExtraLocation(Location.EXTRA_COARSE_LOCATION);
171 if (coarse == null) {
Victoria Leasedf9ec612012-09-11 15:16:25 -0700172 return addCoarseLocationExtraLocked(location);
173 }
Victoria Leasedf9ec612012-09-11 15:16:25 -0700174 if (coarse.getAccuracy() < mAccuracyInMeters) {
175 return addCoarseLocationExtraLocked(location);
176 }
177 return coarse;
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700178 }
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700179 }
180
Victoria Leasedf9ec612012-09-11 15:16:25 -0700181 private Location addCoarseLocationExtraLocked(Location location) {
Victoria Leasedf9ec612012-09-11 15:16:25 -0700182 Location coarse = createCoarseLocked(location);
Victoria Lease09016ab2012-09-16 12:33:15 -0700183 location.setExtraLocation(Location.EXTRA_COARSE_LOCATION, coarse);
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700184 return coarse;
185 }
186
187 /**
188 * Create a coarse location.
189 *
190 * <p>Two techniques are used: random offsets and snap-to-grid.
191 *
192 * <p>First we add a random offset. This mitigates against detecting
193 * grid transitions. Without a random offset it is possible to detect
194 * a users position very accurately when they cross a grid boundary.
195 * The random offset changes very slowly over time, to mitigate against
196 * taking many location samples and averaging them out.
197 *
198 * <p>Second we snap-to-grid (quantize). This has the nice property of
199 * producing stable results, and mitigating against taking many samples
200 * to average out a random offset.
201 */
Victoria Leasedf9ec612012-09-11 15:16:25 -0700202 private Location createCoarseLocked(Location fine) {
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700203 Location coarse = new Location(fine);
204
205 // clean all the optional information off the location, because
206 // this can leak detailed location information
207 coarse.removeBearing();
208 coarse.removeSpeed();
209 coarse.removeAltitude();
210 coarse.setExtras(null);
211
212 double lat = coarse.getLatitude();
213 double lon = coarse.getLongitude();
214
215 // wrap
216 lat = wrapLatitude(lat);
217 lon = wrapLongitude(lon);
218
219 // Step 1) apply a random offset
220 //
221 // The goal of the random offset is to prevent the application
222 // from determining that the device is on a grid boundary
223 // when it crosses from one grid to the next.
224 //
225 // We apply the offset even if the location already claims to be
226 // inaccurate, because it may be more accurate than claimed.
Victoria Leasedf9ec612012-09-11 15:16:25 -0700227 updateRandomOffsetLocked();
228 // perform lon first whilst lat is still within bounds
229 lon += metersToDegreesLongitude(mOffsetLongitudeMeters, lat);
230 lat += metersToDegreesLatitude(mOffsetLatitudeMeters);
231 if (D) Log.d(TAG, String.format("applied offset of %.0f, %.0f (meters)",
232 mOffsetLongitudeMeters, mOffsetLatitudeMeters));
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700233
234 // wrap
235 lat = wrapLatitude(lat);
236 lon = wrapLongitude(lon);
237
238 // Step 2) Snap-to-grid (quantize)
239 //
240 // This is the primary means of obfuscation. It gives nice consistent
241 // results and is very effective at hiding the true location
242 // (as long as you are not sitting on a grid boundary, which
243 // step 1 mitigates).
244 //
245 // Note we quantize the latitude first, since the longitude
246 // quantization depends on the latitude value and so leaks information
247 // about the latitude
Victoria Leasedf9ec612012-09-11 15:16:25 -0700248 double latGranularity = metersToDegreesLatitude(mGridSizeInMeters);
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700249 lat = Math.round(lat / latGranularity) * latGranularity;
Victoria Leasedf9ec612012-09-11 15:16:25 -0700250 double lonGranularity = metersToDegreesLongitude(mGridSizeInMeters, lat);
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700251 lon = Math.round(lon / lonGranularity) * lonGranularity;
252
253 // wrap again
254 lat = wrapLatitude(lat);
255 lon = wrapLongitude(lon);
256
257 // apply
258 coarse.setLatitude(lat);
259 coarse.setLongitude(lon);
Victoria Leasedf9ec612012-09-11 15:16:25 -0700260 coarse.setAccuracy(Math.max(mAccuracyInMeters, coarse.getAccuracy()));
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700261
262 if (D) Log.d(TAG, "fudged " + fine + " to " + coarse);
263 return coarse;
264 }
265
266 /**
267 * Update the random offset over time.
268 *
269 * <p>If the random offset was new for every location
270 * fix then an application can more easily average location results
271 * over time,
272 * especially when the location is near a grid boundary. On the
273 * other hand if the random offset is constant then if an application
274 * found a way to reverse engineer the offset they would be able
275 * to detect location at grid boundaries very accurately. So
276 * we choose a random offset and then very slowly move it, to
277 * make both approaches very hard.
278 *
279 * <p>The random offset does not need to be large, because snap-to-grid
280 * is the primary obfuscation mechanism. It just needs to be large
281 * enough to stop information leakage as we cross grid boundaries.
282 */
283 private void updateRandomOffsetLocked() {
284 long now = SystemClock.elapsedRealtime();
285 if (now < mNextInterval) {
286 return;
287 }
288
289 if (D) Log.d(TAG, String.format("old offset: %.0f, %.0f (meters)",
290 mOffsetLongitudeMeters, mOffsetLatitudeMeters));
291
292 // ok, need to update the random offset
293 mNextInterval = now + CHANGE_INTERVAL_MS;
294
295 mOffsetLatitudeMeters *= PREVIOUS_WEIGHT;
Victoria Leasedf9ec612012-09-11 15:16:25 -0700296 mOffsetLatitudeMeters += NEW_WEIGHT * nextOffsetLocked();
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700297 mOffsetLongitudeMeters *= PREVIOUS_WEIGHT;
Victoria Leasedf9ec612012-09-11 15:16:25 -0700298 mOffsetLongitudeMeters += NEW_WEIGHT * nextOffsetLocked();
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700299
300 if (D) Log.d(TAG, String.format("new offset: %.0f, %.0f (meters)",
301 mOffsetLongitudeMeters, mOffsetLatitudeMeters));
302 }
303
Victoria Leasedf9ec612012-09-11 15:16:25 -0700304 private double nextOffsetLocked() {
305 return mRandom.nextGaussian() * mStandardDeviationInMeters;
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700306 }
307
308 private static double wrapLatitude(double lat) {
309 if (lat > MAX_LATITUDE) {
310 lat = MAX_LATITUDE;
311 }
312 if (lat < -MAX_LATITUDE) {
313 lat = -MAX_LATITUDE;
314 }
315 return lat;
316 }
317
318 private static double wrapLongitude(double lon) {
319 lon %= 360.0; // wraps into range (-360.0, +360.0)
320 if (lon >= 180.0) {
321 lon -= 360.0;
322 }
323 if (lon < -180.0) {
324 lon += 360.0;
325 }
326 return lon;
327 }
328
329 private static double metersToDegreesLatitude(double distance) {
330 return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR;
331 }
332
333 /**
334 * Requires latitude since longitudinal distances change with distance from equator.
335 */
336 private static double metersToDegreesLongitude(double distance, double lat) {
337 return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / Math.cos(Math.toRadians(lat));
338 }
339
340 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
341 pw.println(String.format("offset: %.0f, %.0f (meters)", mOffsetLongitudeMeters,
342 mOffsetLatitudeMeters));
343 }
Victoria Leasedf9ec612012-09-11 15:16:25 -0700344
345 /**
346 * This is the main control: call this to set the best location accuracy
347 * allowed for coarse applications and all derived values.
348 */
349 private void setAccuracyInMetersLocked(float accuracyInMeters) {
350 mAccuracyInMeters = Math.max(accuracyInMeters, MINIMUM_ACCURACY_IN_METERS);
351 if (D) {
352 Log.d(TAG, "setAccuracyInMetersLocked: new accuracy = " + mAccuracyInMeters);
353 }
354 mGridSizeInMeters = mAccuracyInMeters;
355 mStandardDeviationInMeters = mGridSizeInMeters / 4.0;
356 }
357
358 /**
359 * Same as setAccuracyInMetersLocked without the pre-lock requirement.
360 */
361 private void setAccuracyInMeters(float accuracyInMeters) {
362 synchronized (mLock) {
363 setAccuracyInMetersLocked(accuracyInMeters);
364 }
365 }
366
367 /**
368 * Loads the coarse accuracy value from secure settings.
369 */
370 private float loadCoarseAccuracy() {
371 String newSetting = Settings.Secure.getString(mContext.getContentResolver(),
372 COARSE_ACCURACY_CONFIG_NAME);
373 if (D) {
374 Log.d(TAG, "loadCoarseAccuracy: newSetting = \"" + newSetting + "\"");
375 }
376 if (newSetting == null) {
377 return DEFAULT_ACCURACY_IN_METERS;
378 }
379 try {
380 return Float.parseFloat(newSetting);
381 } catch (NumberFormatException e) {
382 return DEFAULT_ACCURACY_IN_METERS;
383 }
384 }
Nick Pelly74fa7ea2012-08-13 19:36:38 -0700385}