Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7c700afc authored by Etan Cohen's avatar Etan Cohen
Browse files

[RTT] Add LCI and LCR structures

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

Bug: 71643239
Test: unit tests
Change-Id: I1ebe4f46d219901761758de58d73815ccfe6e815
parent 9bc9d4ce
Loading
Loading
Loading
Loading
+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);
    }
}
+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);
    }
}
+74 −7
Original line number Diff line number Diff line
@@ -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);
    }
}
+96 −5
Original line number Diff line number Diff line
@@ -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();
    }
}