summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ludovic Barman <ludovicb@google.com> 2024-12-13 03:22:19 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2024-12-13 03:22:19 -0800
commitc3fc17ebaa433f3df0c00e69213e86a91fd6a35e (patch)
tree1d14e6c350aef8db44293b35afca509314fe583c
parenta632f1469ebd8aaa62afe5e137fe2916d7b2be84 (diff)
parentafd9d8e38e8307d3f5aea0e726d96731bd332b35 (diff)
Merge "LocationFudger: cache must fetch default when missing" into main
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudger.java56
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudgerCache.java7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java45
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java14
4 files changed, 104 insertions, 18 deletions
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 881538237746..bbd8aa1aac44 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -16,6 +16,9 @@
package com.android.server.location.fudger;
+import static com.android.internal.location.geometry.S2CellIdUtils.LAT_INDEX;
+import static com.android.internal.location.geometry.S2CellIdUtils.LNG_INDEX;
+
import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.location.Location;
@@ -184,31 +187,33 @@ public class LocationFudger {
synchronized (this) {
cacheCopy = mLocationFudgerCache;
}
-
+ double[] coarsened = new double[] {0.0, 0.0};
// TODO(b/381204398): To ensure a safe rollout, two algorithms co-exist. The first is the
// new density-based algorithm, while the second is the traditional coarsening algorithm.
// Once rollout is done, clean up the unused algorithm.
+ // The new algorithm is applied if and only if (1) the flag is on, (2) the cache has been
+ // set, and (3) the cache has successfully queried the provider for the default coarsening
+ // value.
if (Flags.populationDensityProvider() && Flags.densityBasedCoarseLocations()
- && cacheCopy != null && cacheCopy.hasDefaultValue()) {
- int level = cacheCopy.getCoarseningLevel(latitude, longitude);
- double[] center = snapToCenterOfS2Cell(latitude, longitude, level);
- latitude = center[S2CellIdUtils.LAT_INDEX];
- longitude = center[S2CellIdUtils.LNG_INDEX];
+ && cacheCopy != null) {
+ if (cacheCopy.hasDefaultValue()) {
+ // New algorithm that snaps to the center of a S2 cell.
+ int level = cacheCopy.getCoarseningLevel(latitude, longitude);
+ coarsened = snapToCenterOfS2Cell(latitude, longitude, level);
+ } else {
+ // Try to fetch the default value. The answer won't come in time, but will be used
+ // for the next location to coarsen.
+ cacheCopy.fetchDefaultCoarseningLevelIfNeeded();
+ // Previous algorithm that snaps to a grid of width mAccuracyM.
+ coarsened = snapToGrid(latitude, longitude);
+ }
} else {
- // quantize location by snapping to a grid. this is the primary means of obfuscation. it
- // gives nice consistent results and is very effective at hiding the true location (as
- // long as you are not sitting on a grid boundary, which the random offsets mitigate).
- //
- // note that we quantize the latitude first, since the longitude quantization depends on
- // the latitude value and so leaks information about the latitude
- double latGranularity = metersToDegreesLatitude(mAccuracyM);
- latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
- double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
- longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ // Previous algorithm that snaps to a grid of width mAccuracyM.
+ coarsened = snapToGrid(latitude, longitude);
}
- coarse.setLatitude(latitude);
- coarse.setLongitude(longitude);
+ coarse.setLatitude(coarsened[LAT_INDEX]);
+ coarse.setLongitude(coarsened[LNG_INDEX]);
coarse.setAccuracy(Math.max(mAccuracyM, coarse.getAccuracy()));
synchronized (this) {
@@ -219,6 +224,21 @@ public class LocationFudger {
return coarse;
}
+ // quantize location by snapping to a grid. this is the primary means of obfuscation. it
+ // gives nice consistent results and is very effective at hiding the true location (as
+ // long as you are not sitting on a grid boundary, which the random offsets mitigate).
+ //
+ // note that we quantize the latitude first, since the longitude quantization depends on
+ // the latitude value and so leaks information about the latitude
+ private double[] snapToGrid(double latitude, double longitude) {
+ double[] center = new double[] {0.0, 0.0};
+ double latGranularity = metersToDegreesLatitude(mAccuracyM);
+ center[LAT_INDEX] = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
+ double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
+ center[LNG_INDEX] = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ return center;
+ }
+
@VisibleForTesting
protected double[] snapToCenterOfS2Cell(double latDegrees, double lngDegrees, int level) {
long leafCell = S2CellIdUtils.fromLatLngDegrees(latDegrees, lngDegrees);
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
index ce8bec8f0147..19ec38ce8ccc 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
@@ -76,6 +76,13 @@ public class LocationFudgerCache {
asyncFetchDefaultCoarseningLevel();
}
+ /** If the cache's default coarsening value hasn't been set, asynchronously fetches it. */
+ public void fetchDefaultCoarseningLevelIfNeeded() {
+ if (!hasDefaultValue()) {
+ asyncFetchDefaultCoarseningLevel();
+ }
+ }
+
/** Returns true if the cache has successfully received a default value from the provider. */
public boolean hasDefaultValue() {
synchronized (mLock) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
index 6b7eda26b945..c89048a06ce2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
@@ -280,6 +280,51 @@ public class LocationFudgerCacheTest {
eq(POINT_IN_TIMES_SQUARE[1]), eq(numAdditionalCells), any());
}
+ @Test
+ public void fetchDefaultCoarseningLevelIfNeeded_withDefaultValue_doesNotQueryProvider()
+ throws RemoteException {
+ // Arrange.
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider, times(1)).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(10);
+
+ assertThat(cache.hasDefaultValue()).isTrue();
+
+ // Act.
+ cache.fetchDefaultCoarseningLevelIfNeeded();
+
+ // Assert. The method is not called again.
+ verify(provider, times(1)).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void fetchDefaultCoarseningLevelIfNeeded_withoutDefaultValue_doesQueryProvider()
+ throws RemoteException {
+ // Arrange.
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider, times(1)).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onError();
+
+ assertThat(cache.hasDefaultValue()).isFalse();
+
+ // Act.
+ cache.fetchDefaultCoarseningLevelIfNeeded();
+
+ // Assert. The method is called again.
+ verify(provider, times(2)).getDefaultCoarseningLevel(any());
+ }
@Test
public void locationFudgerCache_canContainUpToMaxSizeItems() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index 49ff041e10b7..2e4652e6f48b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -222,6 +222,20 @@ public class LocationFudgerTest {
}
@Test
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledButNoDefaultValue_defaultIsFetched() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POPULATION_DENSITY_PROVIDER);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+ doReturn(false).when(cache).hasDefaultValue();
+
+ mFudger.setLocationFudgerCache(cache);
+
+ mFudger.createCoarse(createLocation("test", mRandom));
+
+ verify(cache).fetchDefaultCoarseningLevelIfNeeded();
+ }
+
+ @Test
public void testDensityBasedCoarsening_ifFeatureIsEnabledAndDefaultIsSet_cacheIsUsed() {
mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
mSetFlagsRule.enableFlags(Flags.FLAG_POPULATION_DENSITY_PROVIDER);