Loading wifi/java/android/net/wifi/rtt/LocationCivic.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * 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); } } wifi/java/android/net/wifi/rtt/LocationConfigurationInformation.java 0 → 100644 +275 −0 Original line number Diff line number Diff line /* * 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); } } wifi/java/android/net/wifi/rtt/RangingResult.java +74 −7 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -168,6 +176,42 @@ public final class RangingResult implements Parcelable { return mRssi; } /** * @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> Loading Loading @@ -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); } Loading @@ -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); } } }; Loading @@ -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 Loading @@ -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); } } wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java +96 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -46,7 +46,6 @@ import java.util.List; /** * Unit test harness for WifiRttManager class. */ @SmallTest public class WifiRttManagerTest { private WifiRttManager mDut; private TestLooper mMockLooper; Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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); Loading @@ -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(); } } Loading
wifi/java/android/net/wifi/rtt/LocationCivic.java 0 → 100644 +121 −0 Original line number Diff line number Diff line /* * 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); } }
wifi/java/android/net/wifi/rtt/LocationConfigurationInformation.java 0 → 100644 +275 −0 Original line number Diff line number Diff line /* * 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); } }
wifi/java/android/net/wifi/rtt/RangingResult.java +74 −7 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -168,6 +176,42 @@ public final class RangingResult implements Parcelable { return mRssi; } /** * @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> Loading Loading @@ -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); } Loading @@ -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); } } }; Loading @@ -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 Loading @@ -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); } }
wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java +96 −5 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -46,7 +46,6 @@ import java.util.List; /** * Unit test harness for WifiRttManager class. */ @SmallTest public class WifiRttManagerTest { private WifiRttManager mDut; private TestLooper mMockLooper; Loading Loading @@ -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); Loading Loading @@ -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); Loading @@ -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); Loading @@ -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(); } }