[RTT] Add LCI and LCR structures

Baseline LCI & LCR classes and structure (and planned API).

Bug: 71643239
Test: unit tests
Change-Id: I1ebe4f46d219901761758de58d73815ccfe6e815
This commit is contained in:
Etan Cohen
2018-01-05 14:12:58 -08:00
parent 9bc9d4ce0b
commit 7c700afcd9
4 changed files with 566 additions and 12 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
@@ -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>
@@ -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);
}
}

View File

@@ -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();
}
}