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

Commit afd9d8e3 authored by Ludovic Barman's avatar Ludovic Barman
Browse files

LocationFudger: cache must fetch default when missing

The new density-based coarse locations are not used if the cache doesn't have a default coarsening value.
The cache fetches the default when created, however, before this commit, if this initial fetch fails, it never retries.
This changes the LocationFudger behavior so that, if the feature flag is enabled, each time the cache is needed, if the default is not present, it is re-fetched.

Tests:
- atest FrameworksMockingServicesTests:LocationFudgerTest
- atest FrameworksMockingServicesTests:LocationFudgerCacheTest

Test: manual atest on Pixel 7 pro (see above)

Flag: android.location.flags.density_based_coarse_locations

Bug: 381816602

Change-Id: I77ad32802cf6101edb7a269f5c006add2808a751
parent 9d9a4d20
Loading
Loading
Loading
Loading
+38 −18
Original line number Diff line number Diff line
@@ -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()) {
                && cacheCopy != null) {
            if (cacheCopy.hasDefaultValue()) {
                // New algorithm that snaps to the center of a S2 cell.
                int level = cacheCopy.getCoarseningLevel(latitude, longitude);
            double[] center = snapToCenterOfS2Cell(latitude, longitude, level);
            latitude = center[S2CellIdUtils.LAT_INDEX];
            longitude = center[S2CellIdUtils.LNG_INDEX];
                coarsened = snapToCenterOfS2Cell(latitude, longitude, level);
            } 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);
                // 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 {
            // 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);
+7 −0
Original line number Diff line number Diff line
@@ -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) {
+45 −0
Original line number Diff line number Diff line
@@ -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() {
+14 −0
Original line number Diff line number Diff line
@@ -221,6 +221,20 @@ public class LocationFudgerTest {
        verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
    }

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