Loading services/core/java/com/android/server/location/LocationManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -103,6 +103,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.LocalServices; Loading Loading @@ -537,11 +538,16 @@ public class LocationManagerService extends ILocationManager.Stub implements } if (Flags.populationDensityProvider()) { long startTime = System.currentTimeMillis(); setProxyPopulationDensityProvider( ProxyPopulationDensityProvider.createAndRegister(mContext)); int duration = (int) (System.currentTimeMillis() - startTime); if (mPopulationDensityProvider == null) { Log.e(TAG, "no population density provider found"); } FrameworkStatsLog.write(FrameworkStatsLog.POPULATION_DENSITY_PROVIDER_LOADING_REPORTED, /* provider_null= */ (mPopulationDensityProvider == null), /* provider_start_time_millis= */ duration); } if (mPopulationDensityProvider != null && Flags.densityBasedCoarseLocations()) { setLocationFudgerCache(new LocationFudgerCache(mPopulationDensityProvider)); Loading services/core/java/com/android/server/location/fudger/LocationFudger.java +1 −1 Original line number Diff line number Diff line Loading @@ -203,7 +203,7 @@ public class LocationFudger { } 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(); cacheCopy.onDefaultCoarseningLevelNotSet(); // Previous algorithm that snaps to a grid of width mAccuracyM. coarsened = snapToGrid(latitude, longitude); } Loading services/core/java/com/android/server/location/fudger/LocationFudgerCache.java +59 −3 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.location.geometry.S2CellIdUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider; import java.util.Objects; Loading Loading @@ -68,6 +69,15 @@ public class LocationFudgerCache { // The provider that asynchronously provides what is stored in the cache. private final ProxyPopulationDensityProvider mPopulationDensityProvider; // If two calls to logDensityBasedLocsUsed are made in an interval shorter than this value, // the second is dropped. protected static final int LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS = 1000 * 60 * 10; // 10 min // The system time at which the last query to logDensityBasedLocsUsed was made. // Initialized to -LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS, so even if made at time 0, the // first call succeeds. private long mLastQueryToLogDensityBasedLocsUsedMs = -LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS; private static String sTAG = "LocationFudgerCache"; public LocationFudgerCache(@NonNull ProxyPopulationDensityProvider provider) { Loading @@ -76,11 +86,17 @@ public class LocationFudgerCache { asyncFetchDefaultCoarseningLevel(); } /** If the cache's default coarsening value hasn't been set, asynchronously fetches it. */ public void fetchDefaultCoarseningLevelIfNeeded() { /** * Called by the LocationFudger when a query couldn't be fulfilled because the cache isn't set. */ public void onDefaultCoarseningLevelNotSet() { if (!hasDefaultValue()) { asyncFetchDefaultCoarseningLevel(); } logDensityBasedLocsUsed(/* nowMs=*/ System.currentTimeMillis(), /* skippedNoDefault= */ true, /* isCacheHit= */ false, /* defaultCoarseningLevel= */ -1); } /** Returns true if the cache has successfully received a default value from the provider. */ Loading @@ -101,16 +117,45 @@ public class LocationFudgerCache { asyncFetchDefaultCoarseningLevel(); } Long s2CellId = readCacheForLatLng(latitudeDegrees, longitudeDegrees); int defaultLevel = getDefaultCoarseningLevel(); if (s2CellId == null) { // Asynchronously queries the density from the provider. The answer won't come in time, // but it will update the cache for the following queries. refreshCache(latitudeDegrees, longitudeDegrees); return getDefaultCoarseningLevel(); logDensityBasedLocsUsed(/* nowMs=*/ System.currentTimeMillis(), /* skippedNoDefault= */ false, /* isCacheHit= */ false, /* defaultCoarseningLevel= */ defaultLevel); return defaultLevel; } logDensityBasedLocsUsed(/* nowMs=*/ System.currentTimeMillis(), /* skippedNoDefault= */ false, /* isCacheHit= */ true, /* defaultCoarseningLevel= */ defaultLevel); return S2CellIdUtils.getLevel(s2CellId); } /** * A simple wrapper around FrameworkStatsLog.write() that rate-limits the calls. * Returns true on success, false if the call was dropped. */ protected boolean logDensityBasedLocsUsed(long nowMs, boolean skippedNoDefault, boolean isCacheHit, int defaultCoarseningLevel) { if (nowMs - mLastQueryToLogDensityBasedLocsUsedMs < LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS) { return false; } mLastQueryToLogDensityBasedLocsUsedMs = nowMs; FrameworkStatsLog.write(FrameworkStatsLog.DENSITY_BASED_COARSE_LOCATIONS_USAGE_REPORTED, /* skipped_no_default= */ skippedNoDefault, /* is_cache_hit= */ isCacheHit, /* default_coarsening_level= */ defaultCoarseningLevel); return true; } /** * If the cache contains the current location, returns the corresponding S2 cell id. * Otherwise, returns null. Loading Loading @@ -176,15 +221,26 @@ public class LocationFudgerCache { * Queries the population density provider and store the result in the cache. */ private void refreshCache(double latitude, double longitude) { long startTime = System.currentTimeMillis(); IS2CellIdsCallback callback = new IS2CellIdsCallback.Stub() { @Override public void onResult(long[] s2CellIds) { int durationMs = (int) (System.currentTimeMillis() - startTime); FrameworkStatsLog.write( FrameworkStatsLog.DENSITY_BASED_COARSE_LOCATIONS_PROVIDER_QUERY_REPORTED, /* query_duration_millis= */ durationMs, /* is_error= */ false); addToCache(s2CellIds); } @Override public void onError() { Log.e(sTAG, "could not get population density"); int durationMs = (int) (System.currentTimeMillis() - startTime); FrameworkStatsLog.write( FrameworkStatsLog.DENSITY_BASED_COARSE_LOCATIONS_PROVIDER_QUERY_REPORTED, /* query_duration_millis= */ durationMs, /* is_error= */ true); } }; mPopulationDensityProvider.getCoarsenedS2Cells(latitude, longitude, MAX_CACHE_SIZE - 1, Loading services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java +56 −4 Original line number Diff line number Diff line Loading @@ -281,7 +281,7 @@ public class LocationFudgerCacheTest { } @Test public void fetchDefaultCoarseningLevelIfNeeded_withDefaultValue_doesNotQueryProvider() public void onDefaultCoarseningLevelNotSet_withDefaultValue_doesNotQueryProvider() throws RemoteException { // Arrange. ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class); Loading @@ -297,14 +297,14 @@ public class LocationFudgerCacheTest { assertThat(cache.hasDefaultValue()).isTrue(); // Act. cache.fetchDefaultCoarseningLevelIfNeeded(); cache.onDefaultCoarseningLevelNotSet(); // Assert. The method is not called again. verify(provider, times(1)).getDefaultCoarseningLevel(any()); } @Test public void fetchDefaultCoarseningLevelIfNeeded_withoutDefaultValue_doesQueryProvider() public void onDefaultCoarseningLevelNotSet_withoutDefaultValue_doesQueryProvider() throws RemoteException { // Arrange. ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class); Loading @@ -320,7 +320,7 @@ public class LocationFudgerCacheTest { assertThat(cache.hasDefaultValue()).isFalse(); // Act. cache.fetchDefaultCoarseningLevelIfNeeded(); cache.onDefaultCoarseningLevelNotSet(); // Assert. The method is called again. verify(provider, times(2)).getDefaultCoarseningLevel(any()); Loading Loading @@ -383,4 +383,56 @@ public class LocationFudgerCacheTest { assertThat(cache.getCoarseningLevel(latlngs[size - 1][0], latlngs[size - 1][1])) .isEqualTo(0); } @Test public void logDensityBasedLocsUsed_rateLimitsTheSecondCall() { // To avoid having to mock the logger, logDensityBasedLocsUsed returns a boolean indicating // if the log was successful or rate-limited. ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class); LocationFudgerCache cache = new LocationFudgerCache(provider); boolean skippedNoDefault = false; boolean isCacheHit = false; int defaultCoarseningLevel = 3; long time1 = 0; // 7 min later. Can be any value < time1 + LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS long time2 = time1 + 7 * 60 * 1000; boolean success1 = cache.logDensityBasedLocsUsed(time1, skippedNoDefault, isCacheHit, defaultCoarseningLevel); boolean success2 = cache.logDensityBasedLocsUsed(time2, skippedNoDefault, isCacheHit, defaultCoarseningLevel); assertThat(success1).isTrue(); // log OK assertThat(success2).isFalse(); // dropped } @Test public void logDensityBasedLocsUsed_rateLimitOf3rdCall_isNotAffectedByDropped2ndCall() { // To avoid having to mock the logger, logDensityBasedLocsUsed returns a boolean indicating // if the log was successful or rate-limited. ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class); LocationFudgerCache cache = new LocationFudgerCache(provider); boolean skippedNoDefault = false; boolean isCacheHit = false; int defaultCoarseningLevel = 3; long time1 = 0; // 7 min later. Can be any value < time1 + LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS long time2 = time1 + 7 * 60 * 1000; // 11 min later. Can be any value >= time1 + LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS long time3 = time1 + 11 * 60 * 1000; boolean success1 = cache.logDensityBasedLocsUsed(time1, skippedNoDefault, isCacheHit, defaultCoarseningLevel); boolean success2 = cache.logDensityBasedLocsUsed(time2, skippedNoDefault, isCacheHit, defaultCoarseningLevel); boolean success3 = cache.logDensityBasedLocsUsed(time3, skippedNoDefault, isCacheHit, defaultCoarseningLevel); assertThat(success1).isTrue(); // log OK assertThat(success2).isFalse(); // dropped assertThat(success3).isTrue(); // log OK } } services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -232,7 +232,7 @@ public class LocationFudgerTest { mFudger.createCoarse(createLocation("test", mRandom)); verify(cache).fetchDefaultCoarseningLevelIfNeeded(); verify(cache).onDefaultCoarseningLevelNotSet(); } @Test Loading Loading
services/core/java/com/android/server/location/LocationManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -103,6 +103,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.LocalServices; Loading Loading @@ -537,11 +538,16 @@ public class LocationManagerService extends ILocationManager.Stub implements } if (Flags.populationDensityProvider()) { long startTime = System.currentTimeMillis(); setProxyPopulationDensityProvider( ProxyPopulationDensityProvider.createAndRegister(mContext)); int duration = (int) (System.currentTimeMillis() - startTime); if (mPopulationDensityProvider == null) { Log.e(TAG, "no population density provider found"); } FrameworkStatsLog.write(FrameworkStatsLog.POPULATION_DENSITY_PROVIDER_LOADING_REPORTED, /* provider_null= */ (mPopulationDensityProvider == null), /* provider_start_time_millis= */ duration); } if (mPopulationDensityProvider != null && Flags.densityBasedCoarseLocations()) { setLocationFudgerCache(new LocationFudgerCache(mPopulationDensityProvider)); Loading
services/core/java/com/android/server/location/fudger/LocationFudger.java +1 −1 Original line number Diff line number Diff line Loading @@ -203,7 +203,7 @@ public class LocationFudger { } 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(); cacheCopy.onDefaultCoarseningLevelNotSet(); // Previous algorithm that snaps to a grid of width mAccuracyM. coarsened = snapToGrid(latitude, longitude); } Loading
services/core/java/com/android/server/location/fudger/LocationFudgerCache.java +59 −3 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.location.geometry.S2CellIdUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider; import java.util.Objects; Loading Loading @@ -68,6 +69,15 @@ public class LocationFudgerCache { // The provider that asynchronously provides what is stored in the cache. private final ProxyPopulationDensityProvider mPopulationDensityProvider; // If two calls to logDensityBasedLocsUsed are made in an interval shorter than this value, // the second is dropped. protected static final int LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS = 1000 * 60 * 10; // 10 min // The system time at which the last query to logDensityBasedLocsUsed was made. // Initialized to -LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS, so even if made at time 0, the // first call succeeds. private long mLastQueryToLogDensityBasedLocsUsedMs = -LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS; private static String sTAG = "LocationFudgerCache"; public LocationFudgerCache(@NonNull ProxyPopulationDensityProvider provider) { Loading @@ -76,11 +86,17 @@ public class LocationFudgerCache { asyncFetchDefaultCoarseningLevel(); } /** If the cache's default coarsening value hasn't been set, asynchronously fetches it. */ public void fetchDefaultCoarseningLevelIfNeeded() { /** * Called by the LocationFudger when a query couldn't be fulfilled because the cache isn't set. */ public void onDefaultCoarseningLevelNotSet() { if (!hasDefaultValue()) { asyncFetchDefaultCoarseningLevel(); } logDensityBasedLocsUsed(/* nowMs=*/ System.currentTimeMillis(), /* skippedNoDefault= */ true, /* isCacheHit= */ false, /* defaultCoarseningLevel= */ -1); } /** Returns true if the cache has successfully received a default value from the provider. */ Loading @@ -101,16 +117,45 @@ public class LocationFudgerCache { asyncFetchDefaultCoarseningLevel(); } Long s2CellId = readCacheForLatLng(latitudeDegrees, longitudeDegrees); int defaultLevel = getDefaultCoarseningLevel(); if (s2CellId == null) { // Asynchronously queries the density from the provider. The answer won't come in time, // but it will update the cache for the following queries. refreshCache(latitudeDegrees, longitudeDegrees); return getDefaultCoarseningLevel(); logDensityBasedLocsUsed(/* nowMs=*/ System.currentTimeMillis(), /* skippedNoDefault= */ false, /* isCacheHit= */ false, /* defaultCoarseningLevel= */ defaultLevel); return defaultLevel; } logDensityBasedLocsUsed(/* nowMs=*/ System.currentTimeMillis(), /* skippedNoDefault= */ false, /* isCacheHit= */ true, /* defaultCoarseningLevel= */ defaultLevel); return S2CellIdUtils.getLevel(s2CellId); } /** * A simple wrapper around FrameworkStatsLog.write() that rate-limits the calls. * Returns true on success, false if the call was dropped. */ protected boolean logDensityBasedLocsUsed(long nowMs, boolean skippedNoDefault, boolean isCacheHit, int defaultCoarseningLevel) { if (nowMs - mLastQueryToLogDensityBasedLocsUsedMs < LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS) { return false; } mLastQueryToLogDensityBasedLocsUsedMs = nowMs; FrameworkStatsLog.write(FrameworkStatsLog.DENSITY_BASED_COARSE_LOCATIONS_USAGE_REPORTED, /* skipped_no_default= */ skippedNoDefault, /* is_cache_hit= */ isCacheHit, /* default_coarsening_level= */ defaultCoarseningLevel); return true; } /** * If the cache contains the current location, returns the corresponding S2 cell id. * Otherwise, returns null. Loading Loading @@ -176,15 +221,26 @@ public class LocationFudgerCache { * Queries the population density provider and store the result in the cache. */ private void refreshCache(double latitude, double longitude) { long startTime = System.currentTimeMillis(); IS2CellIdsCallback callback = new IS2CellIdsCallback.Stub() { @Override public void onResult(long[] s2CellIds) { int durationMs = (int) (System.currentTimeMillis() - startTime); FrameworkStatsLog.write( FrameworkStatsLog.DENSITY_BASED_COARSE_LOCATIONS_PROVIDER_QUERY_REPORTED, /* query_duration_millis= */ durationMs, /* is_error= */ false); addToCache(s2CellIds); } @Override public void onError() { Log.e(sTAG, "could not get population density"); int durationMs = (int) (System.currentTimeMillis() - startTime); FrameworkStatsLog.write( FrameworkStatsLog.DENSITY_BASED_COARSE_LOCATIONS_PROVIDER_QUERY_REPORTED, /* query_duration_millis= */ durationMs, /* is_error= */ true); } }; mPopulationDensityProvider.getCoarsenedS2Cells(latitude, longitude, MAX_CACHE_SIZE - 1, Loading
services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java +56 −4 Original line number Diff line number Diff line Loading @@ -281,7 +281,7 @@ public class LocationFudgerCacheTest { } @Test public void fetchDefaultCoarseningLevelIfNeeded_withDefaultValue_doesNotQueryProvider() public void onDefaultCoarseningLevelNotSet_withDefaultValue_doesNotQueryProvider() throws RemoteException { // Arrange. ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class); Loading @@ -297,14 +297,14 @@ public class LocationFudgerCacheTest { assertThat(cache.hasDefaultValue()).isTrue(); // Act. cache.fetchDefaultCoarseningLevelIfNeeded(); cache.onDefaultCoarseningLevelNotSet(); // Assert. The method is not called again. verify(provider, times(1)).getDefaultCoarseningLevel(any()); } @Test public void fetchDefaultCoarseningLevelIfNeeded_withoutDefaultValue_doesQueryProvider() public void onDefaultCoarseningLevelNotSet_withoutDefaultValue_doesQueryProvider() throws RemoteException { // Arrange. ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class); Loading @@ -320,7 +320,7 @@ public class LocationFudgerCacheTest { assertThat(cache.hasDefaultValue()).isFalse(); // Act. cache.fetchDefaultCoarseningLevelIfNeeded(); cache.onDefaultCoarseningLevelNotSet(); // Assert. The method is called again. verify(provider, times(2)).getDefaultCoarseningLevel(any()); Loading Loading @@ -383,4 +383,56 @@ public class LocationFudgerCacheTest { assertThat(cache.getCoarseningLevel(latlngs[size - 1][0], latlngs[size - 1][1])) .isEqualTo(0); } @Test public void logDensityBasedLocsUsed_rateLimitsTheSecondCall() { // To avoid having to mock the logger, logDensityBasedLocsUsed returns a boolean indicating // if the log was successful or rate-limited. ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class); LocationFudgerCache cache = new LocationFudgerCache(provider); boolean skippedNoDefault = false; boolean isCacheHit = false; int defaultCoarseningLevel = 3; long time1 = 0; // 7 min later. Can be any value < time1 + LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS long time2 = time1 + 7 * 60 * 1000; boolean success1 = cache.logDensityBasedLocsUsed(time1, skippedNoDefault, isCacheHit, defaultCoarseningLevel); boolean success2 = cache.logDensityBasedLocsUsed(time2, skippedNoDefault, isCacheHit, defaultCoarseningLevel); assertThat(success1).isTrue(); // log OK assertThat(success2).isFalse(); // dropped } @Test public void logDensityBasedLocsUsed_rateLimitOf3rdCall_isNotAffectedByDropped2ndCall() { // To avoid having to mock the logger, logDensityBasedLocsUsed returns a boolean indicating // if the log was successful or rate-limited. ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class); LocationFudgerCache cache = new LocationFudgerCache(provider); boolean skippedNoDefault = false; boolean isCacheHit = false; int defaultCoarseningLevel = 3; long time1 = 0; // 7 min later. Can be any value < time1 + LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS long time2 = time1 + 7 * 60 * 1000; // 11 min later. Can be any value >= time1 + LOG_DENSITY_BASED_LOCS_USED_RATE_LIMIT_MS long time3 = time1 + 11 * 60 * 1000; boolean success1 = cache.logDensityBasedLocsUsed(time1, skippedNoDefault, isCacheHit, defaultCoarseningLevel); boolean success2 = cache.logDensityBasedLocsUsed(time2, skippedNoDefault, isCacheHit, defaultCoarseningLevel); boolean success3 = cache.logDensityBasedLocsUsed(time3, skippedNoDefault, isCacheHit, defaultCoarseningLevel); assertThat(success1).isTrue(); // log OK assertThat(success2).isFalse(); // dropped assertThat(success3).isTrue(); // log OK } }
services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -232,7 +232,7 @@ public class LocationFudgerTest { mFudger.createCoarse(createLocation("test", mRandom)); verify(cache).fetchDefaultCoarseningLevelIfNeeded(); verify(cache).onDefaultCoarseningLevelNotSet(); } @Test Loading