Updated API for one-sided RTT ranging operation (client)

Bug: 163335690
Test: all unit tests continue to pass. Two new tests added.
Change-Id: I01d46af84e3b7e01641af1082b6cc3332cd7525b
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 196566b..9474d97 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -1176,6 +1176,8 @@
     ctor public RangingRequest.Builder();
     method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoint(@NonNull android.net.wifi.ScanResult);
     method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
+    method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addNon80211mcCapableAccessPoint(@NonNull android.net.wifi.ScanResult);
+    method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addNon80211mcCapableAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(@NonNull android.net.MacAddress);
     method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(@NonNull android.net.wifi.aware.PeerHandle);
     method public android.net.wifi.rtt.RangingRequest build();
@@ -1194,6 +1196,7 @@
     method public int getRssi();
     method public int getStatus();
     method @Nullable public android.net.wifi.rtt.ResponderLocation getUnverifiedResponderLocation();
+    method public boolean is80211mcMeasurement();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.rtt.RangingResult> CREATOR;
     field public static final int STATUS_FAIL = 1; // 0x1
diff --git a/framework/java/android/net/wifi/rtt/RangingRequest.java b/framework/java/android/net/wifi/rtt/RangingRequest.java
index 04dfcf2..101b1d2 100644
--- a/framework/java/android/net/wifi/rtt/RangingRequest.java
+++ b/framework/java/android/net/wifi/rtt/RangingRequest.java
@@ -233,9 +233,9 @@
          * which to measure range. The total number of peers added to a request cannot exceed the
          * limit specified by {@link #getMaxPeers()}.
          * <p>
-         * Ranging may not be supported if the Access Point does not support IEEE 802.11mc. Use
-         * {@link ScanResult#is80211mcResponder()} to verify the Access Point's capabilities. If
-         * not supported the result status will be
+         * Ranging will only be supported if the Access Point supports IEEE 802.11mc, also known as
+         * two-sided RTT. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point's
+         * capabilities. If not supported the result status will be
          * {@link RangingResult#STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC}.
          *
          * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
@@ -254,9 +254,9 @@
          * which to measure range. The total number of peers added to a request cannot exceed the
          * limit specified by {@link #getMaxPeers()}.
          * <p>
-         * Ranging may not be supported if the Access Point does not support IEEE 802.11mc. Use
-         * {@link ScanResult#is80211mcResponder()} to verify the Access Point's capabilities. If
-         * not supported the result status will be
+         * Ranging will only be supported if the Access Point supports IEEE 802.11mc, also known as
+         * two-sided RTT. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point's
+         * capabilities. If not supported, the result status will be
          * {@link RangingResult#STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC}.
          *
          * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
@@ -274,6 +274,86 @@
         }
 
         /**
+         * Add the non-802.11mc capable device specified by the {@link ScanResult} to the list of
+         * devices with which to measure range. The total number of peers added to a request cannot
+         * exceed the limit specified by {@link #getMaxPeers()}.
+         * <p>
+         * Accurate ranging cannot be supported if the Access Point does not support IEEE 802.11mc,
+         * and instead an alternate protocol called one-sided RTT will be used with lower
+         * accuracy. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point)s) are
+         * not 802.11mc capable.
+         * <p>
+         * One-sided RTT does not subtract the RTT turnaround time at the Access Point, which can
+         * add hundreds of meters to the estimate. With experimentation it is possible to use this
+         * information to make a statistical estimate of the range by taking multiple measurements
+         * to several Access Points and normalizing the result. For some applications this can be
+         * used to improve range estimates based on Receive Signal Strength Indication (RSSI), but
+         * will not be as accurate as IEEE 802.11mc (two-sided RTT).
+         * <p>
+         * Note: one-sided RTT should only be used if you are very familiar with statistical
+         * estimation techniques.
+         *
+         * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        @NonNull
+        public Builder addNon80211mcCapableAccessPoint(@NonNull ScanResult apInfo) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            if (apInfo == null) {
+                throw new IllegalArgumentException("Null ScanResult!");
+            }
+            if (apInfo.is80211mcResponder()) {
+                throw new IllegalArgumentException("AP supports the 802.11mc protocol.");
+            }
+            return addResponder(ResponderConfig.fromScanResult(apInfo));
+        }
+
+        /**
+         * Add the non-802.11mc capable devices specified by the {@link ScanResult} to the list of
+         * devices with which to measure range. The total number of peers added to a request cannot
+         * exceed the limit specified by {@link #getMaxPeers()}.
+         * <p>
+         * Accurate ranging cannot be supported if the Access Point does not support IEEE 802.11mc,
+         * and instead an alternate protocol called one-sided RTT will be used with lower
+         * accuracy. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point)s) are
+         * not 802.11mc capable.
+         * <p>
+         * One-sided RTT does not subtract the RTT turnaround time at the Access Point, which can
+         * add hundreds of meters to the estimate. With experimentation it is possible to use this
+         * information to make a statistical estimate of the range by taking multiple measurements
+         * to several Access Points and normalizing the result. For some applications this can be
+         * used to improve range estimates based on Receive Signal Strength Indication (RSSI), but
+         * will not be as accurate as IEEE 802.11mc (two-sided RTT).
+         * <p>
+         * Note: one-sided RTT should only be used if you are very familiar with statistical
+         * estimation techniques.
+         *
+         * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        @NonNull
+        public Builder addNon80211mcCapableAccessPoints(@NonNull List<ScanResult> apInfos) {
+            if (!SdkLevel.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            if (apInfos == null) {
+                throw new IllegalArgumentException("Null list of ScanResults!");
+            }
+            for (ScanResult scanResult : apInfos) {
+                if (scanResult.is80211mcResponder()) {
+                    throw new IllegalArgumentException(
+                            "At least one AP supports the 802.11mc protocol.");
+                }
+                addAccessPoint(scanResult);
+            }
+            return this;
+        }
+
+        /**
          * Add the device specified by the {@code peerMacAddress} to the list of devices with
          * which to measure range.
          * <p>
diff --git a/framework/java/android/net/wifi/rtt/RangingResult.java b/framework/java/android/net/wifi/rtt/RangingResult.java
index a065bbc..e2f270c 100644
--- a/framework/java/android/net/wifi/rtt/RangingResult.java
+++ b/framework/java/android/net/wifi/rtt/RangingResult.java
@@ -25,6 +25,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
@@ -72,24 +74,50 @@
      */
     public static final int STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC = 2;
 
-    private final int mStatus;
-    private final MacAddress mMac;
-    private final PeerHandle mPeerHandle;
-    private final int mDistanceMm;
-    private final int mDistanceStdDevMm;
-    private final int mRssi;
-    private final int mNumAttemptedMeasurements;
-    private final int mNumSuccessfulMeasurements;
-    private final byte[] mLci;
-    private final byte[] mLcr;
-    private final ResponderLocation mResponderLocation;
-    private final long mTimestamp;
+    /** @hide */
+    public final int mStatus;
+
+    /** @hide */
+    public final MacAddress mMac;
+
+    /** @hide */
+    public final PeerHandle mPeerHandle;
+
+    /** @hide */
+    public final int mDistanceMm;
+
+    /** @hide */
+    public final int mDistanceStdDevMm;
+
+    /** @hide */
+    public final int mRssi;
+
+    /** @hide */
+    public final int mNumAttemptedMeasurements;
+
+    /** @hide */
+    public final int mNumSuccessfulMeasurements;
+
+    /** @hide */
+    public final byte[] mLci;
+
+    /** @hide */
+    public final byte[] mLcr;
+
+    /** @hide */
+    public final ResponderLocation mResponderLocation;
+
+    /** @hide */
+    public final long mTimestamp;
+
+    /** @hide */
+    public final boolean mIs80211mcMeasurement;
 
     /** @hide */
     public RangingResult(@RangeResultStatus int status, @NonNull MacAddress mac, int distanceMm,
             int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
             int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
-            ResponderLocation responderLocation, long timestamp) {
+            ResponderLocation responderLocation, long timestamp, boolean is80211McMeasurement) {
         mStatus = status;
         mMac = mac;
         mPeerHandle = null;
@@ -102,6 +130,7 @@
         mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
         mResponderLocation = responderLocation;
         mTimestamp = timestamp;
+        mIs80211mcMeasurement = is80211McMeasurement;
     }
 
     /** @hide */
@@ -121,6 +150,7 @@
         mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
         mResponderLocation = responderLocation;
         mTimestamp = timestamp;
+        mIs80211mcMeasurement = true;
     }
 
     /**
@@ -210,8 +240,10 @@
      * {@link #getNumSuccessfulMeasurements()} which at most, if there are no errors, will be 1 less
      * that the number of attempted measurements.
      * <p>
-     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
-     * exception.
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, and if the method
+     * {@link #is80211mcMeasurement()} returns true, otherwise will throw an exception. If the value
+     * is 0, it should be interpreted as no information available, which may occur for one-sided RTT
+     * measurements. Instead {@link RangingRequest#getRttBurstSize()} should be used instead.
      */
     public int getNumAttemptedMeasurements() {
         if (mStatus != STATUS_SUCCESS) {
@@ -321,6 +353,26 @@
         return mTimestamp;
     }
 
+    /**
+     * @return The result is true if the IEEE 802.11mc protocol was used (also known as
+     * two-sided RTT). If the result is false, a one-side RTT result is provided which does not
+     * subtract the turnaround time at the responder.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
+     */
+    public boolean is80211mcMeasurement() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "is80211mcMeasurementResult(): invoked on an invalid result: getStatus()="
+                            + mStatus);
+        }
+        return mIs80211mcMeasurement;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -350,6 +402,7 @@
         dest.writeByteArray(mLcr);
         dest.writeParcelable(mResponderLocation, flags);
         dest.writeLong(mTimestamp);
+        dest.writeBoolean(mIs80211mcMeasurement);
     }
 
     public static final @android.annotation.NonNull Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
@@ -381,6 +434,7 @@
             ResponderLocation responderLocation =
                     in.readParcelable(this.getClass().getClassLoader());
             long timestamp = in.readLong();
+            boolean isllmcMeasurement = in.readBoolean();
             if (peerHandlePresent) {
                 return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
                         numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
@@ -388,7 +442,7 @@
             } else {
                 return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
                         numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
-                        responderLocation, timestamp);
+                        responderLocation, timestamp, isllmcMeasurement);
             }
         }
     };
@@ -404,7 +458,8 @@
                 mNumAttemptedMeasurements).append(", numSuccessfulMeasurements=").append(
                 mNumSuccessfulMeasurements).append(", lci=").append(mLci).append(", lcr=").append(
                 mLcr).append(", responderLocation=").append(mResponderLocation)
-                .append(", timestamp=").append(mTimestamp).append("]").toString();
+                .append(", timestamp=").append(mTimestamp).append(", is80211mcMeasurement=")
+                .append(mIs80211mcMeasurement).append("]").toString();
     }
 
     @Override
@@ -426,6 +481,7 @@
                 && mNumSuccessfulMeasurements == lhs.mNumSuccessfulMeasurements
                 && Arrays.equals(mLci, lhs.mLci) && Arrays.equals(mLcr, lhs.mLcr)
                 && mTimestamp == lhs.mTimestamp
+                && mIs80211mcMeasurement == lhs.mIs80211mcMeasurement
                 && Objects.equals(mResponderLocation, lhs.mResponderLocation);
     }
 
@@ -433,6 +489,6 @@
     public int hashCode() {
         return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
                 mNumAttemptedMeasurements, mNumSuccessfulMeasurements, mLci, mLcr,
-                mResponderLocation, mTimestamp);
+                mResponderLocation, mTimestamp, mIs80211mcMeasurement);
     }
 }
diff --git a/framework/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/framework/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
index c8006fe..b9f6217 100644
--- a/framework/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
+++ b/framework/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
@@ -85,7 +85,7 @@
         List<RangingResult> results = new ArrayList<>();
         results.add(
                 new RangingResult(RangingResult.STATUS_SUCCESS, MacAddress.BROADCAST_ADDRESS, 15, 5,
-                        10, 8, 5, null, null, null, 666));
+                        10, 8, 5, null, null, null, 666, true));
         RangingResultCallback callbackMock = mock(RangingResultCallback.class);
         ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
 
@@ -134,10 +134,13 @@
         // Note: not validating parcel code of ScanResult (assumed to work)
         ScanResult scanResult1 = new ScanResult();
         scanResult1.BSSID = "00:01:02:03:04:05";
+        scanResult1.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
         ScanResult scanResult2 = new ScanResult();
         scanResult2.BSSID = "06:07:08:09:0A:0B";
+        scanResult2.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
         ScanResult scanResult3 = new ScanResult();
         scanResult3.BSSID = "AA:BB:CC:DD:EE:FF";
+        scanResult3.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
         List<ScanResult> scanResults2and3 = new ArrayList<>(2);
         scanResults2and3.add(scanResult2);
         scanResults2and3.add(scanResult3);
@@ -169,9 +172,45 @@
      * Validate the rtt burst size is set correctly when in range.
      */
     @Test
+    public void test802llmcCapableAccessPointFailsForNon11mcBuilderMethods() {
+        ScanResult scanResult1 = new ScanResult();
+        scanResult1.BSSID = "AA:BB:CC:DD:EE:FF";
+        scanResult1.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+
+        // create request for one AP
+        try {
+            RangingRequest.Builder builder = new RangingRequest.Builder();
+            builder.addNon80211mcCapableAccessPoint(scanResult1);
+            fail("Single Access Point was 11mc capable.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        ScanResult scanResult2 = new ScanResult();
+        scanResult2.BSSID = "11:BB:CC:DD:EE:FF";
+        scanResult2.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+        List<ScanResult> scanResults = new ArrayList<>();
+        scanResults.add(scanResult1);
+        scanResults.add(scanResult2);
+
+        // create request for a list of 2 APs.
+        try {
+            RangingRequest.Builder builder = new RangingRequest.Builder();
+            builder.addNon80211mcCapableAccessPoints(scanResults);
+            fail("One Access Point in the List was 11mc capable.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Validate the rtt burst size is set correctly when in range.
+     */
+    @Test
     public void testRangingRequestSetBurstSize() {
         ScanResult scanResult = new ScanResult();
         scanResult.BSSID = "AA:BB:CC:DD:EE:FF";
+        scanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
 
         // create request
         RangingRequest.Builder builder = new RangingRequest.Builder();
@@ -226,6 +265,7 @@
     public void testRangingRequestAtLimit() {
         ScanResult scanResult = new ScanResult();
         scanResult.BSSID = "AA:BB:CC:DD:EE:FF";
+        scanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
         List<ScanResult> scanResultList = new ArrayList<>();
         for (int i = 0; i < RangingRequest.getMaxPeers() - 3; ++i) {
             scanResultList.add(scanResult);
@@ -314,7 +354,8 @@
 
         // RangingResults constructed with a MAC address
         RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
-                numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp);
+                numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp,
+                true);
 
         Parcel parcelW = Parcel.obtain();
         result.writeToParcel(parcelW, 0);
@@ -363,9 +404,11 @@
         byte[] lcr = { };
 
         RangingResult rr1 = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
-                numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp);
+                numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr, null, timestamp,
+                true);
         RangingResult rr2 = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
-                numAttemptedMeasurements, numSuccessfulMeasurements, null, null, null, timestamp);
+                numAttemptedMeasurements, numSuccessfulMeasurements, null, null, null, timestamp,
+                true);
 
         assertEquals(rr1, rr2);
     }