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     private final int mStatus;
76     private final MacAddress mMac;
77     private final PeerHandle mPeerHandle;
78     private final int mDistanceMm;
79     private final int mDistanceStdDevMm;
80     private final int mRssi;
81     private final int mNumAttemptedMeasurements;
82     private final int mNumSuccessfulMeasurements;
83     private final byte[] mLci;
84     private final byte[] mLcr;
85     private final ResponderLocation mResponderLocation;
86     private final long mTimestamp;
87 
88     /** @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)89     public RangingResult(@RangeResultStatus int status, @NonNull MacAddress mac, int distanceMm,
90             int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
91             int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
92             ResponderLocation responderLocation, long timestamp) {
93         mStatus = status;
94         mMac = mac;
95         mPeerHandle = null;
96         mDistanceMm = distanceMm;
97         mDistanceStdDevMm = distanceStdDevMm;
98         mRssi = rssi;
99         mNumAttemptedMeasurements = numAttemptedMeasurements;
100         mNumSuccessfulMeasurements = numSuccessfulMeasurements;
101         mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
102         mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
103         mResponderLocation = responderLocation;
104         mTimestamp = timestamp;
105     }
106 
107     /** @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)108     public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
109             int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
110             int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
111             ResponderLocation responderLocation, long timestamp) {
112         mStatus = status;
113         mMac = null;
114         mPeerHandle = peerHandle;
115         mDistanceMm = distanceMm;
116         mDistanceStdDevMm = distanceStdDevMm;
117         mRssi = rssi;
118         mNumAttemptedMeasurements = numAttemptedMeasurements;
119         mNumSuccessfulMeasurements = numSuccessfulMeasurements;
120         mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
121         mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
122         mResponderLocation = responderLocation;
123         mTimestamp = timestamp;
124     }
125 
126     /**
127      * @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and
128      * {@link #STATUS_FAIL} in case of failure.
129      */
130     @RangeResultStatus
getStatus()131     public int getStatus() {
132         return mStatus;
133     }
134 
135     /**
136      * @return The MAC address of the device whose range measurement was requested. Will correspond
137      * to the MAC address of the device in the {@link RangingRequest}.
138      * <p>
139      * Will return a {@code null} for results corresponding to requests issued using a {@code
140      * PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
141      */
142     @Nullable
getMacAddress()143     public MacAddress getMacAddress() {
144         return mMac;
145     }
146 
147     /**
148      * @return The PeerHandle of the device whose reange measurement was requested. Will correspond
149      * to the PeerHandle of the devices requested using
150      * {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}.
151      * <p>
152      * Will return a {@code null} for results corresponding to requests issued using a MAC address.
153      */
getPeerHandle()154     @Nullable public PeerHandle getPeerHandle() {
155         return mPeerHandle;
156     }
157 
158     /**
159      * @return The distance (in mm) to the device specified by {@link #getMacAddress()} or
160      * {@link #getPeerHandle()}.
161      * <p>
162      * Note: the measured distance may be negative for very close devices.
163      * <p>
164      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
165      * exception.
166      */
getDistanceMm()167     public int getDistanceMm() {
168         if (mStatus != STATUS_SUCCESS) {
169             throw new IllegalStateException(
170                     "getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus);
171         }
172         return mDistanceMm;
173     }
174 
175     /**
176      * @return The standard deviation of the measured distance (in mm) to the device specified by
177      * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated
178      * over the measurements executed in a single RTT burst. The number of measurements is returned
179      * by {@link #getNumSuccessfulMeasurements()} - 0 successful measurements indicate that the
180      * standard deviation is not valid (a valid standard deviation requires at least 2 data points).
181      * <p>
182      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
183      * exception.
184      */
getDistanceStdDevMm()185     public int getDistanceStdDevMm() {
186         if (mStatus != STATUS_SUCCESS) {
187             throw new IllegalStateException(
188                     "getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus);
189         }
190         return mDistanceStdDevMm;
191     }
192 
193     /**
194      * @return The average RSSI, in units of dBm, observed during the RTT measurement.
195      * <p>
196      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
197      * exception.
198      */
getRssi()199     public int getRssi() {
200         if (mStatus != STATUS_SUCCESS) {
201             throw new IllegalStateException(
202                     "getRssi(): invoked on an invalid result: getStatus()=" + mStatus);
203         }
204         return mRssi;
205     }
206 
207     /**
208      * @return The number of attempted measurements used in the RTT exchange resulting in this set
209      * of results. The number of successful measurements is returned by
210      * {@link #getNumSuccessfulMeasurements()} which at most, if there are no errors, will be 1 less
211      * that the number of attempted measurements.
212      * <p>
213      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
214      * exception.
215      */
getNumAttemptedMeasurements()216     public int getNumAttemptedMeasurements() {
217         if (mStatus != STATUS_SUCCESS) {
218             throw new IllegalStateException(
219                     "getNumAttemptedMeasurements(): invoked on an invalid result: getStatus()="
220                             + mStatus);
221         }
222         return mNumAttemptedMeasurements;
223     }
224 
225     /**
226      * @return The number of successful measurements used to calculate the distance and standard
227      * deviation. If the number of successful measurements if 1 then then standard deviation,
228      * returned by {@link #getDistanceStdDevMm()}, is not valid (a 0 is returned for the standard
229      * deviation).
230      * <p>
231      * The total number of measurement attempts is returned by
232      * {@link #getNumAttemptedMeasurements()}. The number of successful measurements will be at
233      * most 1 less then the number of attempted measurements.
234      * <p>
235      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
236      * exception.
237      */
getNumSuccessfulMeasurements()238     public int getNumSuccessfulMeasurements() {
239         if (mStatus != STATUS_SUCCESS) {
240             throw new IllegalStateException(
241                     "getNumSuccessfulMeasurements(): invoked on an invalid result: getStatus()="
242                             + mStatus);
243         }
244         return mNumSuccessfulMeasurements;
245     }
246 
247     /**
248      * @return The unverified responder location represented as {@link ResponderLocation} which
249      * captures location information the responder is programmed to broadcast. The responder
250      * location is referred to as unverified, because we are relying on the device/site
251      * administrator to correctly configure its location data.
252      * <p>
253      * Will return a {@code null} when the location information cannot be parsed.
254      * <p>
255      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
256      * exception.
257      */
258     @Nullable
getUnverifiedResponderLocation()259     public ResponderLocation getUnverifiedResponderLocation() {
260         if (mStatus != STATUS_SUCCESS) {
261             throw new IllegalStateException(
262                     "getUnverifiedResponderLocation(): invoked on an invalid result: getStatus()="
263                             + mStatus);
264         }
265         return mResponderLocation;
266     }
267 
268     /**
269      * @return The Location Configuration Information (LCI) as self-reported by the peer. The format
270      * is specified in the IEEE 802.11-2016 specifications, section 9.4.2.22.10.
271      * <p>
272      * Note: the information is NOT validated - use with caution. Consider validating it with
273      * other sources of information before using it.
274      *
275      * @hide
276      */
277     @SystemApi
278     @NonNull
getLci()279     public byte[] getLci() {
280         if (mStatus != STATUS_SUCCESS) {
281             throw new IllegalStateException(
282                     "getLci(): invoked on an invalid result: getStatus()=" + mStatus);
283         }
284         return mLci;
285     }
286 
287     /**
288      * @return The Location Civic report (LCR) as self-reported by the peer. The format
289      * is specified in the IEEE 802.11-2016 specifications, section 9.4.2.22.13.
290      * <p>
291      * Note: the information is NOT validated - use with caution. Consider validating it with
292      * other sources of information before using it.
293      *
294      * @hide
295      */
296     @SystemApi
297     @NonNull
getLcr()298     public byte[] getLcr() {
299         if (mStatus != STATUS_SUCCESS) {
300             throw new IllegalStateException(
301                     "getReportedLocationCivic(): invoked on an invalid result: getStatus()="
302                             + mStatus);
303         }
304         return mLcr;
305     }
306 
307     /**
308      * @return The timestamp at which the ranging operation was performed. The timestamp is in
309      * milliseconds since boot, including time spent in sleep, corresponding to values provided by
310      * {@link android.os.SystemClock#elapsedRealtime()}.
311      * <p>
312      * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
313      * exception.
314      */
getRangingTimestampMillis()315     public long getRangingTimestampMillis() {
316         if (mStatus != STATUS_SUCCESS) {
317             throw new IllegalStateException(
318                     "getRangingTimestampMillis(): invoked on an invalid result: getStatus()="
319                             + mStatus);
320         }
321         return mTimestamp;
322     }
323 
324     @Override
describeContents()325     public int describeContents() {
326         return 0;
327     }
328 
329     @Override
writeToParcel(Parcel dest, int flags)330     public void writeToParcel(Parcel dest, int flags) {
331         dest.writeInt(mStatus);
332         if (mMac == null) {
333             dest.writeBoolean(false);
334         } else {
335             dest.writeBoolean(true);
336             mMac.writeToParcel(dest, flags);
337         }
338         if (mPeerHandle == null) {
339             dest.writeBoolean(false);
340         } else {
341             dest.writeBoolean(true);
342             dest.writeInt(mPeerHandle.peerId);
343         }
344         dest.writeInt(mDistanceMm);
345         dest.writeInt(mDistanceStdDevMm);
346         dest.writeInt(mRssi);
347         dest.writeInt(mNumAttemptedMeasurements);
348         dest.writeInt(mNumSuccessfulMeasurements);
349         dest.writeByteArray(mLci);
350         dest.writeByteArray(mLcr);
351         dest.writeParcelable(mResponderLocation, flags);
352         dest.writeLong(mTimestamp);
353     }
354 
355     public static final @android.annotation.NonNull Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
356         @Override
357         public RangingResult[] newArray(int size) {
358             return new RangingResult[size];
359         }
360 
361         @Override
362         public RangingResult createFromParcel(Parcel in) {
363             int status = in.readInt();
364             boolean macAddressPresent = in.readBoolean();
365             MacAddress mac = null;
366             if (macAddressPresent) {
367                 mac = MacAddress.CREATOR.createFromParcel(in);
368             }
369             boolean peerHandlePresent = in.readBoolean();
370             PeerHandle peerHandle = null;
371             if (peerHandlePresent) {
372                 peerHandle = new PeerHandle(in.readInt());
373             }
374             int distanceMm = in.readInt();
375             int distanceStdDevMm = in.readInt();
376             int rssi = in.readInt();
377             int numAttemptedMeasurements = in.readInt();
378             int numSuccessfulMeasurements = in.readInt();
379             byte[] lci = in.createByteArray();
380             byte[] lcr = in.createByteArray();
381             ResponderLocation responderLocation =
382                     in.readParcelable(this.getClass().getClassLoader());
383             long timestamp = in.readLong();
384             if (peerHandlePresent) {
385                 return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
386                         numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
387                         responderLocation, timestamp);
388             } else {
389                 return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
390                         numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
391                         responderLocation, timestamp);
392             }
393         }
394     };
395 
396     /** @hide */
397     @Override
toString()398     public String toString() {
399         return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
400                 mMac).append(", peerHandle=").append(
401                 mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(", distanceMm=").append(
402                 mDistanceMm).append(", distanceStdDevMm=").append(mDistanceStdDevMm).append(
403                 ", rssi=").append(mRssi).append(", numAttemptedMeasurements=").append(
404                 mNumAttemptedMeasurements).append(", numSuccessfulMeasurements=").append(
405                 mNumSuccessfulMeasurements).append(", lci=").append(mLci).append(", lcr=").append(
406                 mLcr).append(", responderLocation=").append(mResponderLocation)
407                 .append(", timestamp=").append(mTimestamp).append("]").toString();
408     }
409 
410     @Override
equals(Object o)411     public boolean equals(Object o) {
412         if (this == o) {
413             return true;
414         }
415 
416         if (!(o instanceof RangingResult)) {
417             return false;
418         }
419 
420         RangingResult lhs = (RangingResult) o;
421 
422         return mStatus == lhs.mStatus && Objects.equals(mMac, lhs.mMac) && Objects.equals(
423                 mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
424                 && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
425                 && mNumAttemptedMeasurements == lhs.mNumAttemptedMeasurements
426                 && mNumSuccessfulMeasurements == lhs.mNumSuccessfulMeasurements
427                 && Arrays.equals(mLci, lhs.mLci) && Arrays.equals(mLcr, lhs.mLcr)
428                 && mTimestamp == lhs.mTimestamp
429                 && Objects.equals(mResponderLocation, lhs.mResponderLocation);
430     }
431 
432     @Override
hashCode()433     public int hashCode() {
434         return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
435                 mNumAttemptedMeasurements, mNumSuccessfulMeasurements, mLci, mLcr,
436                 mResponderLocation, mTimestamp);
437     }
438 }
439