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

Commit c3fc17eb authored by Ludovic Barman's avatar Ludovic Barman Committed by Android (Google) Code Review
Browse files

Merge "LocationFudger: cache must fetch default when missing" into main

parents a632f146 afd9d8e3
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);