1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.wifi.rtt;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.net.MacAddress;
24 import android.net.wifi.aware.PeerHandle;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.Objects;
33 
34 /**
35  * Ranging result for a request started by
36  * {@link WifiRttManager#startRanging(RangingRequest, java.util.concurrent.Executor, RangingResultCallback)}.
37  * Results are returned in {@link RangingResultCallback#onRangingResults(List)}.
38  * <p>
39  * A ranging result is the distance measurement result for a single device specified in the
40  * {@link RangingRequest}.
41  */
42 public final class RangingResult implements Parcelable {
43     private static final String TAG = "RangingResult";
44     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
45 
46     /** @hide */
47     @IntDef({STATUS_SUCCESS, STATUS_FAIL, STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC})
48     @Retention(RetentionPolicy.SOURCE)
49     public @interface RangeResultStatus {
50     }
51 
52     /**
53      * Individual range request status, {@link #getStatus()}. Indicates ranging operation was
54      * successful and distance value is valid.
55      */
56     public static final int STATUS_SUCCESS = 0;
57 
58     /**
59      * Individual range request status, {@link #getStatus()}. Indicates ranging operation failed
60      * and the distance value is invalid.
61      */
62     public static final int STATUS_FAIL = 1;
63 
64     /**
65      * Individual range request status, {@link #getStatus()}. Indicates that the ranging operation
66      * failed because the specified peer does not support IEEE 802.11mc RTT operations. Support by
67      * an Access Point can be confirmed using
68      * {@link android.net.wifi.ScanResult#is80211mcResponder()}.
69      * <p>
70      * On such a failure, the individual result fields of {@link RangingResult} such as
71      * {@link RangingResult#getDistanceMm()} are invalid.
72      */
73     public static final int STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC = 2;
74 
75     /** @hide */
76     public final int mStatus;
77 
78     /** @hide */
79     public final MacAddress mMac;
80 
81     /** @hide */
82     public final PeerHandle mPeerHandle;
83 
84     /** @hide */
85     public final int mDistanceMm;
86 
87     /** @hide */
88     public final int mDistanceStdDevMm;
89 
90     /** @hide */
91     public final int mRssi;
92 
93     /** @hide */
94     public final int mNumAttemptedMeasurements;
95 
96     /** @hide */
97     public final int mNumSuccessfulMeasurements;
98 
99     /** @hide */
100     public final byte[] mLci;
101 
102     /** @hide */
103     public final byte[] mLcr;
104 
105     /** @hide */
106     public final ResponderLocation mResponderLocation;
107 
108     /** @hide */
109     public final long mTimestamp;
110 
111     /** @hide */
112     public final boolean mIs80211mcMeasurement;
113 
114     /** @hide */
RangingResult(@angeResultStatus int status, @NonNull MacAddress mac, int distanceMm, int distanceStdDevMm, int rssi, int numAttemptedMeasurements, int numSuccessfulMeasurements, byte[] lci, byte[] lcr, ResponderLocation responderLocation, long timestamp, boolean is80211McMeasurement)115     public RangingResult(@RangeResultStatus int status, @NonNull MacAddress mac, int distanceMm,
116             int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
117             int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
118             ResponderLocation responderLocation, long timestamp, boolean is80211McMeasurement) {
119         mStatus = status;
120         mMac = mac;
121         mPeerHandle = null;
122         mDistanceMm = distanceMm;
123         mDistanceStdDevMm = distanceStdDevMm;
124         mRssi = rssi;
125         mNumAttemptedMeasurements = numAttemptedMeasurements;
126         mNumSuccessfulMeasurements = numSuccessfulMeasurements;
127         mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
128         mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
129         mResponderLocation = responderLocation;
130         mTimestamp = timestamp;
131         mIs80211mcMeasurement = is80211McMeasurement;
132     }
133 
134     /** @hide */
RangingResult(@angeResultStatus int status, PeerHandle peerHandle, int distanceMm, int distanceStdDevMm, int rssi, int numAttemptedMeasurements, int numSuccessfulMeasurements, byte[] lci, byte[] lcr, ResponderLocation responderLocation, long timestamp)135     public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
136             int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
137             int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
138             ResponderLocation responderLocation, long timestamp) {
139         mStatus = status;
140         mMac = null;
141         mPeerHandle = peerHandle;
142         mDistanceMm = distanceMm;
143         mDistanceStdDevMm = distanceStdDevMm;
144         mRssi = rssi;
145         mNumAttemptedMeasurements = numAttemptedMeasurements;
146         mNumSuccessfulMeasurements = numSuccessfulMeasurements;
147         mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
148         mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
149         mResponderLocation = responderLocation;
150         mTimestamp = timestamp;
151         mIs80211mcMeasurement = true;
152     }
153 
154     /**
155      * @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and
156      * {@link #STATUS_FAIL} in case of failure.
157      */
158     @RangeResultStatus
getStatus()159     public int getStatus() {
160         return mStatus;
161     }
162 
163     /**
164      * @return The MAC address of the device whose range measurement was requested. Will correspond
165      * to the MAC address of the device in the {@link RangingRequest}.
166      * <p>
167      * Will return a {@code null} for results corresponding to requests issued using a {@code
168      * PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
169      */
170     @Nullable
getMacAddress()171     public MacAddress getMacAddress() {
172         return mMac;
173     }
174 
175     /**
176      * @return The PeerHandle of the device whose reange measurement was requested. Will correspond
177      * to the PeerHandle of the devices requested using
178      * {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}.
179      * <p>
180      * Will return a {@code null} for results corresponding to requests issued using a MAC address.
181      */
getPeerHandle()182     @Nullable public PeerHandle getPeerHandle() {
183         return mPeerHandle;
184     }
185 
186     /**
187      * @return The distance (in mm) to the device specified by {@link #getMacAddress()} or
188      * {@link #getPeerHandle()}.
189      * <p>
190      * Note: the measured distance may be negative for very close devices.
191      * <p>
192      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
193      * exception.
194      */
getDistanceMm()195     public int getDistanceMm() {
196         if (mStatus != STATUS_SUCCESS) {
197             throw new IllegalStateException(
198                     "getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus);
199         }
200         return mDistanceMm;
201     }
202 
203     /**
204      * @return The standard deviation of the measured distance (in mm) to the device specified by
205      * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated
206      * over the measurements executed in a single RTT burst. The number of measurements is returned
207      * by {@link #getNumSuccessfulMeasurements()} - 0 successful measurements indicate that the
208      * standard deviation is not valid (a valid standard deviation requires at least 2 data points).
209      * <p>
210      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
211      * exception.
212      */
getDistanceStdDevMm()213     public int getDistanceStdDevMm() {
214         if (mStatus != STATUS_SUCCESS) {
215             throw new IllegalStateException(
216                     "getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus);
217         }
218         return mDistanceStdDevMm;
219     }
220 
221     /**
222      * @return The average RSSI, in units of dBm, observed during the RTT measurement.
223      * <p>
224      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
225      * exception.
226      */
getRssi()227     public int getRssi() {
228         if (mStatus != STATUS_SUCCESS) {
229             throw new IllegalStateException(
230                     "getRssi(): invoked on an invalid result: getStatus()=" + mStatus);
231         }
232         return mRssi;
233     }
234 
235     /**
236      * @return The number of attempted measurements used in the RTT exchange resulting in this set
237      * of results. The number of successful measurements is returned by
238      * {@link #getNumSuccessfulMeasurements()} which at most, if there are no errors, will be 1
239      * less than the number of attempted measurements.
240      * <p>
241      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
242      * exception. If the value is 0, it should be interpreted as no information available, which may
243      * occur for one-sided RTT measurements. Instead {@link RangingRequest#getRttBurstSize()}
244      * should be used instead.
245      */
getNumAttemptedMeasurements()246     public int getNumAttemptedMeasurements() {
247         if (mStatus != STATUS_SUCCESS) {
248             throw new IllegalStateException(
249                     "getNumAttemptedMeasurements(): invoked on an invalid result: getStatus()="
250                             + mStatus);
251         }
252         return mNumAttemptedMeasurements;
253     }
254 
255     /**
256      * @return The number of successful measurements used to calculate the distance and standard
257      * deviation. If the number of successful measurements if 1 then then standard deviation,
258      * returned by {@link #getDistanceStdDevMm()}, is not valid (a 0 is returned for the standard
259      * deviation).
260      * <p>
261      * The total number of measurement attempts is returned by
262      * {@link #getNumAttemptedMeasurements()}. The number of successful measurements will be at
263      * most 1 less then the number of attempted measurements.
264      * <p>
265      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
266      * exception.
267      */
getNumSuccessfulMeasurements()268     public int getNumSuccessfulMeasurements() {
269         if (mStatus != STATUS_SUCCESS) {
270             throw new IllegalStateException(
271                     "getNumSuccessfulMeasurements(): invoked on an invalid result: getStatus()="
272                             + mStatus);
273         }
274         return mNumSuccessfulMeasurements;
275     }
276 
277     /**
278      * @return The unverified responder location represented as {@link ResponderLocation} which
279      * captures location information the responder is programmed to broadcast. The responder
280      * location is referred to as unverified, because we are relying on the device/site
281      * administrator to correctly configure its location data.
282      * <p>
283      * Will return a {@code null} when the location information cannot be parsed.
284      * <p>
285      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
286      * exception.
287      */
288     @Nullable
getUnverifiedResponderLocation()289     public ResponderLocation getUnverifiedResponderLocation() {
290         if (mStatus != STATUS_SUCCESS) {
291             throw new IllegalStateException(
292                     "getUnverifiedResponderLocation(): invoked on an invalid result: getStatus()="
293                             + mStatus);
294         }
295         return mResponderLocation;
296     }
297 
298     /**
299      * @return The Location Configuration Information (LCI) as self-reported by the peer. The format
300      * is specified in the IEEE 802.11-2016 specifications, section 9.4.2.22.10.
301      * <p>
302      * Note: the information is NOT validated - use with caution. Consider validating it with
303      * other sources of information before using it.
304      *
305      * @hide
306      */
307     @SystemApi
308     @NonNull
getLci()309     public byte[] getLci() {
310         if (mStatus != STATUS_SUCCESS) {
311             throw new IllegalStateException(
312                     "getLci(): invoked on an invalid result: getStatus()=" + mStatus);
313         }
314         return mLci;
315     }
316 
317     /**
318      * @return The Location Civic report (LCR) as self-reported by the peer. The format
319      * is specified in the IEEE 802.11-2016 specifications, section 9.4.2.22.13.
320      * <p>
321      * Note: the information is NOT validated - use with caution. Consider validating it with
322      * other sources of information before using it.
323      *
324      * @hide
325      */
326     @SystemApi
327     @NonNull
getLcr()328     public byte[] getLcr() {
329         if (mStatus != STATUS_SUCCESS) {
330             throw new IllegalStateException(
331                     "getReportedLocationCivic(): invoked on an invalid result: getStatus()="
332                             + mStatus);
333         }
334         return mLcr;
335     }
336 
337     /**
338      * @return The timestamp at which the ranging operation was performed. The timestamp is in
339      * milliseconds since boot, including time spent in sleep, corresponding to values provided by
340      * {@link android.os.SystemClock#elapsedRealtime()}.
341      * <p>
342      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
343      * exception.
344      */
getRangingTimestampMillis()345     public long getRangingTimestampMillis() {
346         if (mStatus != STATUS_SUCCESS) {
347             throw new IllegalStateException(
348                     "getRangingTimestampMillis(): invoked on an invalid result: getStatus()="
349                             + mStatus);
350         }
351         return mTimestamp;
352     }
353 
354     /**
355      * @return The result is true if the IEEE 802.11mc protocol was used (also known as
356      * two-sided RTT). If the result is false, a one-side RTT result is provided which does not
357      * subtract the turnaround time at the responder.
358      * <p>
359      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
360      * exception.
361      */
is80211mcMeasurement()362     public boolean is80211mcMeasurement() {
363         if (mStatus != STATUS_SUCCESS) {
364             throw new IllegalStateException(
365                     "is80211mcMeasurementResult(): invoked on an invalid result: getStatus()="
366                             + mStatus);
367         }
368         return mIs80211mcMeasurement;
369     }
370 
371     @Override
describeContents()372     public int describeContents() {
373         return 0;
374     }
375 
376     @Override
writeToParcel(Parcel dest, int flags)377     public void writeToParcel(Parcel dest, int flags) {
378         dest.writeInt(mStatus);
379         if (mMac == null) {
380             dest.writeBoolean(false);
381         } else {
382             dest.writeBoolean(true);
383             mMac.writeToParcel(dest, flags);
384         }
385         if (mPeerHandle == null) {
386             dest.writeBoolean(false);
387         } else {
388             dest.writeBoolean(true);
389             dest.writeInt(mPeerHandle.peerId);
390         }
391         dest.writeInt(mDistanceMm);
392         dest.writeInt(mDistanceStdDevMm);
393         dest.writeInt(mRssi);
394         dest.writeInt(mNumAttemptedMeasurements);
395         dest.writeInt(mNumSuccessfulMeasurements);
396         dest.writeByteArray(mLci);
397         dest.writeByteArray(mLcr);
398         dest.writeParcelable(mResponderLocation, flags);
399         dest.writeLong(mTimestamp);
400         dest.writeBoolean(mIs80211mcMeasurement);
401     }
402 
403     public static final @android.annotation.NonNull Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
404         @Override
405         public RangingResult[] newArray(int size) {
406             return new RangingResult[size];
407         }
408 
409         @Override
410         public RangingResult createFromParcel(Parcel in) {
411             int status = in.readInt();
412             boolean macAddressPresent = in.readBoolean();
413             MacAddress mac = null;
414             if (macAddressPresent) {
415                 mac = MacAddress.CREATOR.createFromParcel(in);
416             }
417             boolean peerHandlePresent = in.readBoolean();
418             PeerHandle peerHandle = null;
419             if (peerHandlePresent) {
420                 peerHandle = new PeerHandle(in.readInt());
421             }
422             int distanceMm = in.readInt();
423             int distanceStdDevMm = in.readInt();
424             int rssi = in.readInt();
425             int numAttemptedMeasurements = in.readInt();
426             int numSuccessfulMeasurements = in.readInt();
427             byte[] lci = in.createByteArray();
428             byte[] lcr = in.createByteArray();
429             ResponderLocation responderLocation =
430                     in.readParcelable(this.getClass().getClassLoader());
431             long timestamp = in.readLong();
432             boolean isllmcMeasurement = in.readBoolean();
433             if (peerHandlePresent) {
434                 return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
435                         numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
436                         responderLocation, timestamp);
437             } else {
438                 return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
439                         numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
440                         responderLocation, timestamp, isllmcMeasurement);
441             }
442         }
443     };
444 
445     /** @hide */
446     @Override
toString()447     public String toString() {
448         return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
449                 mMac).append(", peerHandle=").append(
450                 mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(", distanceMm=").append(
451                 mDistanceMm).append(", distanceStdDevMm=").append(mDistanceStdDevMm).append(
452                 ", rssi=").append(mRssi).append(", numAttemptedMeasurements=").append(
453                 mNumAttemptedMeasurements).append(", numSuccessfulMeasurements=").append(
454                 mNumSuccessfulMeasurements).append(", lci=").append(mLci).append(", lcr=").append(
455                 mLcr).append(", responderLocation=").append(mResponderLocation)
456                 .append(", timestamp=").append(mTimestamp).append(", is80211mcMeasurement=")
457                 .append(mIs80211mcMeasurement).append("]").toString();
458     }
459 
460     @Override
equals(Object o)461     public boolean equals(Object o) {
462         if (this == o) {
463             return true;
464         }
465 
466         if (!(o instanceof RangingResult)) {
467             return false;
468         }
469 
470         RangingResult lhs = (RangingResult) o;
471 
472         return mStatus == lhs.mStatus && Objects.equals(mMac, lhs.mMac) && Objects.equals(
473                 mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
474                 && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
475                 && mNumAttemptedMeasurements == lhs.mNumAttemptedMeasurements
476                 && mNumSuccessfulMeasurements == lhs.mNumSuccessfulMeasurements
477                 && Arrays.equals(mLci, lhs.mLci) && Arrays.equals(mLcr, lhs.mLcr)
478                 && mTimestamp == lhs.mTimestamp
479                 && mIs80211mcMeasurement == lhs.mIs80211mcMeasurement
480                 && Objects.equals(mResponderLocation, lhs.mResponderLocation);
481     }
482 
483     @Override
hashCode()484     public int hashCode() {
485         return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
486                 mNumAttemptedMeasurements, mNumSuccessfulMeasurements, mLci, mLcr,
487                 mResponderLocation, mTimestamp, mIs80211mcMeasurement);
488     }
489 }
490