diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java index d46b4d2c3be38..eb73b69737a26 100644 --- a/location/java/android/location/altitude/AltitudeConverter.java +++ b/location/java/android/location/altitude/AltitudeConverter.java @@ -169,4 +169,28 @@ public final class AltitudeConverter { double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds); addMslAltitude(params, s2CellIds, geoidHeightsMeters, location); } + + /** + * Same as {@link #addMslAltitudeToLocation(Context, Location)} except that data will not be + * loaded from raw assets. Returns true if a Mean Sea Level altitude is added to the + * {@code location}; otherwise, returns false and leaves the {@code location} unchanged. + * + * @hide + */ + public boolean addMslAltitudeToLocation(@NonNull Location location) { + validate(location); + MapParamsProto params = GeoidHeightMap.getParams(); + if (params == null) { + return false; + } + + long[] s2CellIds = findMapSquare(params, location); + double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, s2CellIds); + if (geoidHeightsMeters == null) { + return false; + } + + addMslAltitude(params, s2CellIds, geoidHeightsMeters, location); + return true; + } } diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java index 6430eb4efad2c..73b6ab556ad07 100644 --- a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java +++ b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java @@ -76,6 +76,17 @@ public final class GeoidHeightMap { } } + /** + * Same as {@link #getParams(Context)} except that null is returned if the singleton parameter + * instance is not yet initialized. + */ + @Nullable + public static MapParamsProto getParams() { + synchronized (sLock) { + return sParams; + } + } + private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) { return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level); } @@ -99,7 +110,8 @@ public final class GeoidHeightMap { S2TileProto[] tiles = new S2TileProto[len]; for (int i = 0; i < len; i++) { if (s2CellIds[i] != 0) { - tiles[i] = tileFunction.getTile(s2CellIds[i]); + long cacheKey = getCacheKey(params, s2CellIds[i]); + tiles[i] = tileFunction.getTile(cacheKey); } values[i] = Double.NaN; } @@ -207,6 +219,18 @@ public final class GeoidHeightMap { return (iOrJ >> (S2CellIdUtils.MAX_LEVEL - params.mapS2Level)) % widthOrHeight; } + /** + * Throws an {@link IllegalArgumentException} if the {@code s2CellIds} has an invalid length or + * ID. + */ + private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) { + Preconditions.checkArgument(s2CellIds.length == 4); + for (long s2CellId : s2CellIds) { + Preconditions.checkArgument( + s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level); + } + } + /** * Returns the geoid heights in meters associated with the map cells identified by * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a @@ -215,12 +239,7 @@ public final class GeoidHeightMap { @NonNull public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context, @NonNull long[] s2CellIds) throws IOException { - Preconditions.checkArgument(s2CellIds.length == 4); - for (long s2CellId : s2CellIds) { - Preconditions.checkArgument( - s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level); - } - + validate(params, s2CellIds); double[] heightsMeters = new double[s2CellIds.length]; if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) { return heightsMeters; @@ -233,6 +252,21 @@ public final class GeoidHeightMap { throw new IOException("Unable to calculate geoid heights from raw assets."); } + /** + * Same as {@link #readGeoidHeights(MapParamsProto, Context, long[])} except that data will not + * be loaded from raw assets. Returns the heights if present for all non-zero IDs; otherwise, + * returns null. + */ + @Nullable + public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) { + validate(params, s2CellIds); + double[] heightsMeters = new double[s2CellIds.length]; + if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) { + return heightsMeters; + } + return null; + } + /** * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells * identified by {@code s2CellIds}. Returns true if heights are present for all non-zero IDs; @@ -297,11 +331,7 @@ public final class GeoidHeightMap { mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles); } - return s2CellId -> { - if (s2CellId == 0) { - return null; - } - long cacheKey = getCacheKey(params, s2CellId); + return cacheKey -> { for (int i = 0; i < cacheKeys.length; i++) { if (cacheKeys[i] == cacheKey) { return loadedTiles[i]; @@ -321,8 +351,8 @@ public final class GeoidHeightMap { long[] s2CellIds = new long[numMapCellsPerCacheTile]; double[] values = new double[numMapCellsPerCacheTile]; - // Each cache key identifies a different sub-tile of the disk tile. - TileFunction diskTileFunction = s2CellId -> diskTile; + // Each cache key identifies a different sub-tile of the same disk tile. + TileFunction diskTileFunction = cacheKey -> diskTile; for (int i = diskTokenIndex; i < len; i++) { if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex]) || loadedTiles[i] != null) { @@ -358,10 +388,10 @@ public final class GeoidHeightMap { } } - /** Defines a function-like object to retrieve tiles for map cells. */ + /** Defines a function-like object to retrieve tiles for cache keys. */ private interface TileFunction { @Nullable - S2TileProto getTile(long s2CellId); + S2TileProto getTile(long cacheKey); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java new file mode 100644 index 0000000000000..0d9aeb59dd075 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.altitude; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import android.content.Context; +import android.location.Location; +import android.location.altitude.AltitudeConverter; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.MediumTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +@MediumTest +@RunWith(AndroidJUnit4.class) +public class AltitudeConverterTest { + + private AltitudeConverter mAltitudeConverter; + private Context mContext; + + @Before + public void setUp() { + mAltitudeConverter = new AltitudeConverter(); + mContext = ApplicationProvider.getApplicationContext(); + } + + @Test + public void testAddMslAltitudeToLocation_expectedBehavior() throws IOException { + // Interpolates between bffffc, 955554, and 000004. + Location location = new Location(""); + location.setLatitude(-35.246789); + location.setLongitude(-44.962683); + location.setAltitude(-1); + location.setVerticalAccuracyMeters(1); + // Requires data to be loaded from raw assets. + assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isFalse(); + assertThat(location.hasMslAltitude()).isFalse(); + assertThat(location.hasMslAltitudeAccuracy()).isFalse(); + // Loads data from raw assets. + mAltitudeConverter.addMslAltitudeToLocation(mContext, location); + assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076); + assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); + assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); + + // Again interpolates between bffffc, 955554, and 000004. + location = new Location(""); + location.setLatitude(-35.246789); + location.setLongitude(-44.962683); + location.setAltitude(-1); + location.setVerticalAccuracyMeters(1); + // Requires no data to be loaded from raw assets. + assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isTrue(); + assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076); + assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); + assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); + // Results in same outcome. + mAltitudeConverter.addMslAltitudeToLocation(mContext, location); + assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1076); + assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); + assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); + + // Interpolate between 955554, 000004, 00000c, and 95554c - no vertical accuracy. + location = new Location(""); + location.setLatitude(-35.176383); + location.setLongitude(-44.962683); + location.setAltitude(-1); + location.setVerticalAccuracyMeters(-1); // Invalid vertical accuracy + // Requires no data to be loaded from raw assets. + assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isTrue(); + assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1919); + assertThat(location.hasMslAltitudeAccuracy()).isFalse(); + // Results in same outcome. + mAltitudeConverter.addMslAltitudeToLocation(mContext, location); + assertThat(location.getMslAltitudeMeters()).isWithin(2).of(5.1919); + assertThat(location.hasMslAltitudeAccuracy()).isFalse(); + + // Interpolates somewhere else more interesting, i.e., Hawaii. + location = new Location(""); + location.setLatitude(19.545519); + location.setLongitude(-155.998774); + location.setAltitude(-1); + location.setVerticalAccuracyMeters(1); + // Requires data to be loaded from raw assets. + assertThat(mAltitudeConverter.addMslAltitudeToLocation(location)).isFalse(); + assertThat(location.hasMslAltitude()).isFalse(); + assertThat(location.hasMslAltitudeAccuracy()).isFalse(); + // Loads data from raw assets. + mAltitudeConverter.addMslAltitudeToLocation(mContext, location); + assertThat(location.getMslAltitudeMeters()).isWithin(2).of(-19.2359); + assertThat(location.getMslAltitudeAccuracyMeters()).isGreaterThan(1f); + assertThat(location.getMslAltitudeAccuracyMeters()).isLessThan(1.1f); + } + + @Test + public void testAddMslAltitudeToLocation_invalidLatitudeThrows() { + Location location = new Location(""); + location.setLongitude(-44.962683); + location.setAltitude(-1); + + location.setLatitude(Double.NaN); + assertThrows(IllegalArgumentException.class, + () -> mAltitudeConverter.addMslAltitudeToLocation(location)); + + location.setLatitude(91); + assertThrows(IllegalArgumentException.class, + () -> mAltitudeConverter.addMslAltitudeToLocation(location)); + + location.setLatitude(-91); + assertThrows(IllegalArgumentException.class, + () -> mAltitudeConverter.addMslAltitudeToLocation(location)); + } + + @Test + public void testAddMslAltitudeToLocation_invalidLongitudeThrows() { + Location location = new Location(""); + location.setLatitude(-35.246789); + location.setAltitude(-1); + + location.setLongitude(Double.NaN); + assertThrows(IllegalArgumentException.class, + () -> mAltitudeConverter.addMslAltitudeToLocation(location)); + + location.setLongitude(181); + assertThrows(IllegalArgumentException.class, + () -> mAltitudeConverter.addMslAltitudeToLocation(location)); + + location.setLongitude(-181); + assertThrows(IllegalArgumentException.class, + () -> mAltitudeConverter.addMslAltitudeToLocation(location)); + } + + @Test + public void testAddMslAltitudeToLocation_invalidAltitudeThrows() { + Location location = new Location(""); + location.setLatitude(-35.246789); + location.setLongitude(-44.962683); + + assertThrows(IllegalArgumentException.class, + () -> mAltitudeConverter.addMslAltitudeToLocation(location)); + + location.setAltitude(Double.NaN); + assertThrows(IllegalArgumentException.class, + () -> mAltitudeConverter.addMslAltitudeToLocation(location)); + + location.setAltitude(Double.POSITIVE_INFINITY); + assertThrows(IllegalArgumentException.class, + () -> mAltitudeConverter.addMslAltitudeToLocation(location)); + } +}