diff options
4 files changed, 566 insertions, 12 deletions
diff --git a/wifi/java/android/net/wifi/rtt/LocationCivic.java b/wifi/java/android/net/wifi/rtt/LocationCivic.java new file mode 100644 index 000000000000..bdf318946178 --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/LocationCivic.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2018 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 android.net.wifi.rtt; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Location Civic Report (LCR). + * <p> + * The information matches the IEEE 802.11-2016 LCR report. + * <p> + * Note: depending on the mechanism by which this information is returned (i.e. the API which + * returns an instance of this class) it is possibly Self Reported (by the peer). In such a case + * the information is NOT validated - use with caution. Consider validating it with other sources + * of information before using it. + * + * @hide + */ +public final class LocationCivic implements Parcelable { + private final byte[] mData; + + /** + * Parse the raw LCR information element (byte array) and extract the LocationCivic structure. + * + * Note: any parsing errors or invalid/unexpected errors will result in a null being returned. + * + * @hide + */ + @Nullable + public static LocationCivic parseInformationElement(byte id, byte[] data) { + // TODO + return null; + } + + /** @hide */ + public LocationCivic(byte[] data) { + mData = data; + } + + /** + * Return the Location Civic data reported by the peer. + * + * @return An arbitrary location information. + */ + public byte[] getData() { + return mData; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(mData); + } + + /** @hide */ + public static final Parcelable.Creator<LocationCivic> CREATOR = + new Parcelable.Creator<LocationCivic>() { + @Override + public LocationCivic[] newArray(int size) { + return new LocationCivic[size]; + } + + @Override + public LocationCivic createFromParcel(Parcel in) { + byte[] data = in.createByteArray(); + + return new LocationCivic(data); + } + }; + + /** @hide */ + @Override + public String toString() { + return new StringBuilder("LCR: data=").append(Arrays.toString(mData)).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof LocationCivic)) { + return false; + } + + LocationCivic lhs = (LocationCivic) o; + + return Arrays.equals(mData, lhs.mData); + } + + @Override + public int hashCode() { + return Objects.hash(mData); + } +} diff --git a/wifi/java/android/net/wifi/rtt/LocationConfigurationInformation.java b/wifi/java/android/net/wifi/rtt/LocationConfigurationInformation.java new file mode 100644 index 000000000000..32ff2744ffa5 --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/LocationConfigurationInformation.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2018 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 android.net.wifi.rtt; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * The Device Location Configuration Information (LCI) specifies the location information of a peer + * device (e.g. an Access Point). + * <p> + * The information matches the IEEE 802.11-2016 LCI report (Location configuration information + * report). + * <p> + * Note: depending on the mechanism by which this information is returned (i.e. the API which + * returns an instance of this class) it is possibly Self Reported (by the peer). In such a case + * the information is NOT validated - use with caution. Consider validating it with other sources + * of information before using it. + * + * @hide PLANNED_API + */ +public final class LocationConfigurationInformation implements Parcelable { + /** @hide */ + @IntDef({ + ALTITUDE_UNKNOWN, ALTITUDE_IN_METERS, ALTITUDE_IN_FLOORS }) + @Retention(RetentionPolicy.SOURCE) + public @interface AltitudeTypes { + } + + /** + * Define an Altitude Type returned by {@link #getAltitudeType()}. Indicates that the location + * does not specify an altitude or altitude uncertainty. The corresponding methods, + * {@link #getAltitude()} and {@link #getAltitudeUncertainty()} are not valid and will throw + * an exception. + */ + public static final int ALTITUDE_UNKNOWN = 0; + + /** + * Define an Altitude Type returned by {@link #getAltitudeType()}. Indicates that the location + * specifies the altitude and altitude uncertainty in meters. The corresponding methods, + * {@link #getAltitude()} and {@link #getAltitudeUncertainty()} return a valid value in meters. + */ + public static final int ALTITUDE_IN_METERS = 1; + + /** + * Define an Altitude Type returned by {@link #getAltitudeType()}. Indicates that the + * location specifies the altitude in floors, and does not specify an altitude uncertainty. + * The {@link #getAltitude()} method returns valid value in floors, and the + * {@link #getAltitudeUncertainty()} method is not valid and will throw an exception. + */ + public static final int ALTITUDE_IN_FLOORS = 2; + + private final double mLatitude; + private final double mLatitudeUncertainty; + private final double mLongitude; + private final double mLongitudeUncertainty; + private final int mAltitudeType; + private final double mAltitude; + private final double mAltitudeUncertainty; + + /** + * Parse the raw LCI information element (byte array) and extract the + * LocationConfigurationInformation structure. + * + * Note: any parsing errors or invalid/unexpected errors will result in a null being returned. + * + * @hide + */ + @Nullable + public static LocationConfigurationInformation parseInformationElement(byte id, byte[] data) { + // TODO + return null; + } + + /** @hide */ + public LocationConfigurationInformation(double latitude, double latitudeUncertainty, + double longitude, double longitudeUncertainty, @AltitudeTypes int altitudeType, + double altitude, double altitudeUncertainty) { + mLatitude = latitude; + mLatitudeUncertainty = latitudeUncertainty; + mLongitude = longitude; + mLongitudeUncertainty = longitudeUncertainty; + mAltitudeType = altitudeType; + mAltitude = altitude; + mAltitudeUncertainty = altitudeUncertainty; + } + + /** + * Get latitude in degrees. Values are per WGS 84 reference system. Valid values are between + * -90 and 90. + * + * @return Latitude in degrees. + */ + public double getLatitude() { + return mLatitude; + } + + /** + * Get the uncertainty of the latitude {@link #getLatitude()} in degrees. A value of 0 indicates + * an unknown uncertainty. + * + * @return Uncertainty of the latitude in degrees. + */ + public double getLatitudeUncertainty() { + return mLatitudeUncertainty; + } + + /** + * Get longitude in degrees. Values are per WGS 84 reference system. Valid values are between + * -180 and 180. + * + * @return Longitude in degrees. + */ + public double getLongitude() { + return mLongitude; + } + + /** + * Get the uncertainty of the longitude {@link #getLongitude()} ()} in degrees. A value of 0 + * indicates an unknown uncertainty. + * + * @return Uncertainty of the longitude in degrees. + */ + public double getLongitudeUncertainty() { + return mLongitudeUncertainty; + } + + /** + * Specifies the type of the altitude measurement returned by {@link #getAltitude()} and + * {@link #getAltitudeUncertainty()}. The possible values are: + * <li>{@link #ALTITUDE_UNKNOWN}: The altitude and altitude uncertainty are not provided. + * <li>{@link #ALTITUDE_IN_METERS}: The altitude and altitude uncertainty are provided in + * meters. Values are per WGS 84 reference system. + * <li>{@link #ALTITUDE_IN_FLOORS}: The altitude is provided in floors, the altitude uncertainty + * is not provided. + * + * @return The type of the altitude and altitude uncertainty. + */ + public @AltitudeTypes int getAltitudeType() { + return mAltitudeType; + } + + /** + * The altitude is interpreted according to the {@link #getAltitudeType()}. The possible values + * are: + * <li>{@link #ALTITUDE_UNKNOWN}: The altitude is not provided - this method will throw an + * exception. + * <li>{@link #ALTITUDE_IN_METERS}: The altitude is provided in meters. Values are per WGS 84 + * reference system. + * <li>{@link #ALTITUDE_IN_FLOORS}: The altitude is provided in floors. + * + * @return Altitude value whose meaning is specified by {@link #getAltitudeType()}. + */ + public double getAltitude() { + if (mAltitudeType == ALTITUDE_UNKNOWN) { + throw new IllegalStateException( + "getAltitude(): invoked on an invalid type: getAltitudeType()==UNKNOWN"); + } + return mAltitude; + } + + /** + * Only valid if the the {@link #getAltitudeType()} is equal to {@link #ALTITUDE_IN_METERS} - + * otherwise this method will throw an exception. + * <p> + * Get the uncertainty of the altitude {@link #getAltitude()} in meters. A value of 0 + * indicates an unknown uncertainty. + * + * @return Uncertainty of the altitude in meters. + */ + public double getAltitudeUncertainty() { + if (mAltitudeType != ALTITUDE_IN_METERS) { + throw new IllegalStateException( + "getAltitude(): invoked on an invalid type: getAltitudeType()!=IN_METERS"); + } + return mAltitudeUncertainty; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeDouble(mLatitude); + dest.writeDouble(mLatitudeUncertainty); + dest.writeDouble(mLongitude); + dest.writeDouble(mLongitudeUncertainty); + dest.writeInt(mAltitudeType); + dest.writeDouble(mAltitude); + dest.writeDouble(mAltitudeUncertainty); + } + + /** @hide */ + public static final Creator<LocationConfigurationInformation> CREATOR = + new Creator<LocationConfigurationInformation>() { + @Override + public LocationConfigurationInformation[] newArray(int size) { + return new LocationConfigurationInformation[size]; + } + + @Override + public LocationConfigurationInformation createFromParcel(Parcel in) { + double latitude = in.readDouble(); + double latitudeUnc = in.readDouble(); + double longitude = in.readDouble(); + double longitudeUnc = in.readDouble(); + int altitudeType = in.readInt(); + double altitude = in.readDouble(); + double altitudeUnc = in.readDouble(); + + return new LocationConfigurationInformation(latitude, latitudeUnc, longitude, + longitudeUnc, altitudeType, altitude, altitudeUnc); + } + }; + + /** @hide */ + @Override + public String toString() { + return new StringBuilder("LCI: latitude=").append(mLatitude).append( + ", latitudeUncertainty=").append(mLatitudeUncertainty).append( + ", longitude=").append(mLongitude).append(", longitudeUncertainty=").append( + mLongitudeUncertainty).append(", altitudeType=").append(mAltitudeType).append( + ", altitude=").append(mAltitude).append(", altitudeUncertainty=").append( + mAltitudeUncertainty).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof LocationConfigurationInformation)) { + return false; + } + + LocationConfigurationInformation lhs = (LocationConfigurationInformation) o; + + return mLatitude == lhs.mLatitude && mLatitudeUncertainty == lhs.mLatitudeUncertainty + && mLongitude == lhs.mLongitude + && mLongitudeUncertainty == lhs.mLongitudeUncertainty + && mAltitudeType == lhs.mAltitudeType && mAltitude == lhs.mAltitude + && mAltitudeUncertainty == lhs.mAltitudeUncertainty; + } + + @Override + public int hashCode() { + return Objects.hash(mLatitude, mLatitudeUncertainty, mLongitude, mLongitudeUncertainty, + mAltitudeType, mAltitude, mAltitudeUncertainty); + } +} diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java index d5ca8f7f9fb0..ec4a7596cfa5 100644 --- a/wifi/java/android/net/wifi/rtt/RangingResult.java +++ b/wifi/java/android/net/wifi/rtt/RangingResult.java @@ -65,29 +65,37 @@ public final class RangingResult implements Parcelable { private final int mDistanceMm; private final int mDistanceStdDevMm; private final int mRssi; + private final LocationConfigurationInformation mLci; + private final LocationCivic mLcr; private final long mTimestamp; /** @hide */ public RangingResult(@RangeResultStatus int status, @NonNull MacAddress mac, int distanceMm, - int distanceStdDevMm, int rssi, long timestamp) { + int distanceStdDevMm, int rssi, LocationConfigurationInformation lci, LocationCivic lcr, + long timestamp) { mStatus = status; mMac = mac; mPeerHandle = null; mDistanceMm = distanceMm; mDistanceStdDevMm = distanceStdDevMm; mRssi = rssi; + mLci = lci; + mLcr = lcr; mTimestamp = timestamp; } /** @hide */ public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm, - int distanceStdDevMm, int rssi, long timestamp) { + int distanceStdDevMm, int rssi, LocationConfigurationInformation lci, LocationCivic lcr, + long timestamp) { mStatus = status; mMac = null; mPeerHandle = peerHandle; mDistanceMm = distanceMm; mDistanceStdDevMm = distanceStdDevMm; mRssi = rssi; + mLci = lci; + mLcr = lcr; mTimestamp = timestamp; } @@ -169,6 +177,42 @@ public final class RangingResult implements Parcelable { } /** + * @return The Location Configuration Information (LCI) as self-reported by the peer. + * <p> + * Note: the information is NOT validated - use with caution. Consider validating it with + * other sources of information before using it. + * + * @hide PLANNED_API + */ + @Nullable + public LocationConfigurationInformation getReportedLocationConfigurationInformation() { + if (mStatus != STATUS_SUCCESS) { + throw new IllegalStateException( + "getReportedLocationConfigurationInformation(): invoked on an invalid result: " + + "getStatus()=" + mStatus); + } + return mLci; + } + + /** + * @return The Location Civic report (LCR) as self-reported by the peer. + * <p> + * Note: the information is NOT validated - use with caution. Consider validating it with + * other sources of information before using it. + * + * @hide PLANNED_API + */ + @Nullable + public LocationCivic getReportedLocationCivic() { + if (mStatus != STATUS_SUCCESS) { + throw new IllegalStateException( + "getReportedLocationCivic(): invoked on an invalid result: getStatus()=" + + mStatus); + } + return mLcr; + } + + /** * @return The timestamp, in us since boot, at which the ranging operation was performed. * <p> * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an @@ -205,6 +249,18 @@ public final class RangingResult implements Parcelable { dest.writeInt(mDistanceMm); dest.writeInt(mDistanceStdDevMm); dest.writeInt(mRssi); + if (mLci == null) { + dest.writeBoolean(false); + } else { + dest.writeBoolean(true); + mLci.writeToParcel(dest, flags); + } + if (mLcr == null) { + dest.writeBoolean(false); + } else { + dest.writeBoolean(true); + mLcr.writeToParcel(dest, flags); + } dest.writeLong(mTimestamp); } @@ -230,13 +286,23 @@ public final class RangingResult implements Parcelable { int distanceMm = in.readInt(); int distanceStdDevMm = in.readInt(); int rssi = in.readInt(); + boolean lciPresent = in.readBoolean(); + LocationConfigurationInformation lci = null; + if (lciPresent) { + lci = LocationConfigurationInformation.CREATOR.createFromParcel(in); + } + boolean lcrPresent = in.readBoolean(); + LocationCivic lcr = null; + if (lcrPresent) { + lcr = LocationCivic.CREATOR.createFromParcel(in); + } long timestamp = in.readLong(); if (peerHandlePresent) { return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi, - timestamp); + lci, lcr, timestamp); } else { return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi, - timestamp); + lci, lcr, timestamp); } } }; @@ -248,8 +314,8 @@ public final class RangingResult implements Parcelable { mMac).append(", peerHandle=").append( mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(", distanceMm=").append( mDistanceMm).append(", distanceStdDevMm=").append(mDistanceStdDevMm).append( - ", rssi=").append(mRssi).append(", timestamp=").append(mTimestamp).append( - "]").toString(); + ", rssi=").append(mRssi).append(", lci=").append(mLci).append(", lcr=").append( + mLcr).append(", timestamp=").append(mTimestamp).append("]").toString(); } @Override @@ -267,12 +333,13 @@ public final class RangingResult implements Parcelable { return mStatus == lhs.mStatus && Objects.equals(mMac, lhs.mMac) && Objects.equals( mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi + && Objects.equals(mLci, lhs.mLci) && Objects.equals(mLcr, lhs.mLcr) && mTimestamp == lhs.mTimestamp; } @Override public int hashCode() { return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi, - mTimestamp); + mLci, mLcr, mTimestamp); } } diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java index 72e95b93e741..41c7f8644e2e 100644 --- a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java +++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java @@ -17,6 +17,7 @@ package android.net.wifi.rtt; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -32,7 +33,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.test.TestLooper; -import android.test.suitebuilder.annotation.SmallTest; import org.junit.Before; import org.junit.Test; @@ -46,7 +46,6 @@ import java.util.List; /** * Unit test harness for WifiRttManager class. */ -@SmallTest public class WifiRttManagerTest { private WifiRttManager mDut; private TestLooper mMockLooper; @@ -80,7 +79,7 @@ public class WifiRttManagerTest { List<RangingResult> results = new ArrayList<>(); results.add( new RangingResult(RangingResult.STATUS_SUCCESS, MacAddress.BROADCAST_ADDRESS, 15, 5, - 10, 666)); + 10, null, null, 666)); RangingResultCallback callbackMock = mock(RangingResultCallback.class); ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class); @@ -236,10 +235,23 @@ public class WifiRttManagerTest { int distanceStdDevCm = 10; int rssi = 5; long timestamp = System.currentTimeMillis(); + double latitude = 5.5; + double latitudeUncertainty = 6.5; + double longitude = 7.5; + double longitudeUncertainty = 8.5; + int altitudeType = LocationConfigurationInformation.ALTITUDE_IN_METERS; + double altitude = 9.5; + double altitudeUncertainty = 55.5; + byte[] lcrData = { 0x1, 0x2, 0x3, 0xA, 0xB, 0xC }; + + LocationConfigurationInformation lci = new LocationConfigurationInformation(latitude, + latitudeUncertainty, longitude, longitudeUncertainty, altitudeType, altitude, + altitudeUncertainty); + LocationCivic lcr = new LocationCivic(lcrData); // RangingResults constructed with a MAC address RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, - timestamp); + lci, lcr, timestamp); Parcel parcelW = Parcel.obtain(); result.writeToParcel(parcelW, 0); @@ -255,7 +267,7 @@ public class WifiRttManagerTest { // RangingResults constructed with a PeerHandle result = new RangingResult(status, peerHandle, distanceCm, distanceStdDevCm, rssi, - timestamp); + null, null, timestamp); parcelW = Parcel.obtain(); result.writeToParcel(parcelW, 0); @@ -269,4 +281,83 @@ public class WifiRttManagerTest { assertEquals(result, rereadResult); } + + /** + * Validate that LocationConfigurationInformation parcel works (produces same object on + * write/read). + */ + @Test + public void testLciParcel() { + double latitude = 1.5; + double latitudeUncertainty = 2.5; + double longitude = 3.5; + double longitudeUncertainty = 4.5; + int altitudeType = LocationConfigurationInformation.ALTITUDE_IN_FLOORS; + double altitude = 5.5; + double altitudeUncertainty = 6.5; + + LocationConfigurationInformation lci = new LocationConfigurationInformation(latitude, + latitudeUncertainty, longitude, longitudeUncertainty, altitudeType, altitude, + altitudeUncertainty); + + Parcel parcelW = Parcel.obtain(); + lci.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + LocationConfigurationInformation rereadLci = + LocationConfigurationInformation.CREATOR.createFromParcel(parcelR); + + assertEquals(lci, rereadLci); + } + + /** + * Validate that the LCI throws an exception when accessing invalid fields an certain altitude + * types. + */ + @Test + public void testLciInvalidAltitudeFieldAccess() { + boolean exceptionThrown; + LocationConfigurationInformation lci = new LocationConfigurationInformation(0, 0, 0, 0, + LocationConfigurationInformation.ALTITUDE_UNKNOWN, 0, 0); + + // UNKNOWN - invalid altitude & altitude uncertainty + exceptionThrown = false; + try { + lci.getAltitude(); + } catch (IllegalStateException e) { + exceptionThrown = true; + } + assertTrue("UNKNOWN / getAltitude()", exceptionThrown); + + exceptionThrown = false; + try { + lci.getAltitudeUncertainty(); + } catch (IllegalStateException e) { + exceptionThrown = true; + } + assertTrue("UNKNOWN / getAltitudeUncertainty()", exceptionThrown); + + lci = new LocationConfigurationInformation(0, 0, 0, 0, + LocationConfigurationInformation.ALTITUDE_IN_FLOORS, 0, 0); + + // FLOORS - invalid altitude uncertainty + exceptionThrown = false; + try { + lci.getAltitudeUncertainty(); + } catch (IllegalStateException e) { + exceptionThrown = true; + } + assertTrue("FLOORS / getAltitudeUncertainty()", exceptionThrown); + + // and good accesses just in case + lci.getAltitude(); + lci = new LocationConfigurationInformation(0, 0, 0, 0, + LocationConfigurationInformation.ALTITUDE_IN_METERS, 0, 0); + lci.getAltitude(); + lci.getAltitudeUncertainty(); + } } |