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