blob: a065bbc4b656f507c660607a514da15b57c0556a [file] [log] [blame]
/*
* Copyright (C) 2017 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.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.MacAddress;
import android.net.wifi.aware.PeerHandle;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Ranging result for a request started by
* {@link WifiRttManager#startRanging(RangingRequest, java.util.concurrent.Executor, RangingResultCallback)}.
* Results are returned in {@link RangingResultCallback#onRangingResults(List)}.
* <p>
* A ranging result is the distance measurement result for a single device specified in the
* {@link RangingRequest}.
*/
public final class RangingResult implements Parcelable {
private static final String TAG = "RangingResult";
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/** @hide */
@IntDef({STATUS_SUCCESS, STATUS_FAIL, STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC})
@Retention(RetentionPolicy.SOURCE)
public @interface RangeResultStatus {
}
/**
* Individual range request status, {@link #getStatus()}. Indicates ranging operation was
* successful and distance value is valid.
*/
public static final int STATUS_SUCCESS = 0;
/**
* Individual range request status, {@link #getStatus()}. Indicates ranging operation failed
* and the distance value is invalid.
*/
public static final int STATUS_FAIL = 1;
/**
* Individual range request status, {@link #getStatus()}. Indicates that the ranging operation
* failed because the specified peer does not support IEEE 802.11mc RTT operations. Support by
* an Access Point can be confirmed using
* {@link android.net.wifi.ScanResult#is80211mcResponder()}.
* <p>
* On such a failure, the individual result fields of {@link RangingResult} such as
* {@link RangingResult#getDistanceMm()} are invalid.
*/
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 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) {
mStatus = status;
mMac = mac;
mPeerHandle = null;
mDistanceMm = distanceMm;
mDistanceStdDevMm = distanceStdDevMm;
mRssi = rssi;
mNumAttemptedMeasurements = numAttemptedMeasurements;
mNumSuccessfulMeasurements = numSuccessfulMeasurements;
mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
mResponderLocation = responderLocation;
mTimestamp = timestamp;
}
/** @hide */
public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
int distanceStdDevMm, int rssi, int numAttemptedMeasurements,
int numSuccessfulMeasurements, byte[] lci, byte[] lcr,
ResponderLocation responderLocation, long timestamp) {
mStatus = status;
mMac = null;
mPeerHandle = peerHandle;
mDistanceMm = distanceMm;
mDistanceStdDevMm = distanceStdDevMm;
mRssi = rssi;
mNumAttemptedMeasurements = numAttemptedMeasurements;
mNumSuccessfulMeasurements = numSuccessfulMeasurements;
mLci = lci == null ? EMPTY_BYTE_ARRAY : lci;
mLcr = lcr == null ? EMPTY_BYTE_ARRAY : lcr;
mResponderLocation = responderLocation;
mTimestamp = timestamp;
}
/**
* @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and
* {@link #STATUS_FAIL} in case of failure.
*/
@RangeResultStatus
public int getStatus() {
return mStatus;
}
/**
* @return The MAC address of the device whose range measurement was requested. Will correspond
* to the MAC address of the device in the {@link RangingRequest}.
* <p>
* Will return a {@code null} for results corresponding to requests issued using a {@code
* PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
*/
@Nullable
public MacAddress getMacAddress() {
return mMac;
}
/**
* @return The PeerHandle of the device whose reange measurement was requested. Will correspond
* to the PeerHandle of the devices requested using
* {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}.
* <p>
* Will return a {@code null} for results corresponding to requests issued using a MAC address.
*/
@Nullable public PeerHandle getPeerHandle() {
return mPeerHandle;
}
/**
* @return The distance (in mm) to the device specified by {@link #getMacAddress()} or
* {@link #getPeerHandle()}.
* <p>
* Note: the measured distance may be negative for very close devices.
* <p>
* Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
* exception.
*/
public int getDistanceMm() {
if (mStatus != STATUS_SUCCESS) {
throw new IllegalStateException(
"getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus);
}
return mDistanceMm;
}
/**
* @return The standard deviation of the measured distance (in mm) to the device specified by
* {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated
* over the measurements executed in a single RTT burst. The number of measurements is returned
* by {@link #getNumSuccessfulMeasurements()} - 0 successful measurements indicate that the
* standard deviation is not valid (a valid standard deviation requires at least 2 data points).
* <p>
* Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
* exception.
*/
public int getDistanceStdDevMm() {
if (mStatus != STATUS_SUCCESS) {
throw new IllegalStateException(
"getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus);
}
return mDistanceStdDevMm;
}
/**
* @return The average RSSI, in units of dBm, observed during the RTT measurement.
* <p>
* Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
* exception.
*/
public int getRssi() {
if (mStatus != STATUS_SUCCESS) {
throw new IllegalStateException(
"getRssi(): invoked on an invalid result: getStatus()=" + mStatus);
}
return mRssi;
}
/**
* @return The number of attempted measurements used in the RTT exchange resulting in this set
* of results. The number of successful measurements is returned by
* {@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.
*/
public int getNumAttemptedMeasurements() {
if (mStatus != STATUS_SUCCESS) {
throw new IllegalStateException(
"getNumAttemptedMeasurements(): invoked on an invalid result: getStatus()="
+ mStatus);
}
return mNumAttemptedMeasurements;
}
/**
* @return The number of successful measurements used to calculate the distance and standard
* deviation. If the number of successful measurements if 1 then then standard deviation,
* returned by {@link #getDistanceStdDevMm()}, is not valid (a 0 is returned for the standard
* deviation).
* <p>
* The total number of measurement attempts is returned by
* {@link #getNumAttemptedMeasurements()}. The number of successful measurements will be at
* most 1 less then the number of attempted measurements.
* <p>
* Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
* exception.
*/
public int getNumSuccessfulMeasurements() {
if (mStatus != STATUS_SUCCESS) {
throw new IllegalStateException(
"getNumSuccessfulMeasurements(): invoked on an invalid result: getStatus()="
+ mStatus);
}
return mNumSuccessfulMeasurements;
}
/**
* @return The unverified responder location represented as {@link ResponderLocation} which
* captures location information the responder is programmed to broadcast. The responder
* location is referred to as unverified, because we are relying on the device/site
* administrator to correctly configure its location data.
* <p>
* Will return a {@code null} when the location information cannot be parsed.
* <p>
* Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
* exception.
*/
@Nullable
public ResponderLocation getUnverifiedResponderLocation() {
if (mStatus != STATUS_SUCCESS) {
throw new IllegalStateException(
"getUnverifiedResponderLocation(): invoked on an invalid result: getStatus()="
+ mStatus);
}
return mResponderLocation;
}
/**
* @return The Location Configuration Information (LCI) as self-reported by the peer. The format
* is specified in the IEEE 802.11-2016 specifications, section 9.4.2.22.10.
* <p>
* Note: the information is NOT validated - use with caution. Consider validating it with
* other sources of information before using it.
*
* @hide
*/
@SystemApi
@NonNull
public byte[] getLci() {
if (mStatus != STATUS_SUCCESS) {
throw new IllegalStateException(
"getLci(): invoked on an invalid result: getStatus()=" + mStatus);
}
return mLci;
}
/**
* @return The Location Civic report (LCR) as self-reported by the peer. The format
* is specified in the IEEE 802.11-2016 specifications, section 9.4.2.22.13.
* <p>
* Note: the information is NOT validated - use with caution. Consider validating it with
* other sources of information before using it.
*
* @hide
*/
@SystemApi
@NonNull
public byte[] getLcr() {
if (mStatus != STATUS_SUCCESS) {
throw new IllegalStateException(
"getReportedLocationCivic(): invoked on an invalid result: getStatus()="
+ mStatus);
}
return mLcr;
}
/**
* @return The timestamp at which the ranging operation was performed. The timestamp is in
* milliseconds since boot, including time spent in sleep, corresponding to values provided by
* {@link android.os.SystemClock#elapsedRealtime()}.
* <p>
* Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
* exception.
*/
public long getRangingTimestampMillis() {
if (mStatus != STATUS_SUCCESS) {
throw new IllegalStateException(
"getRangingTimestampMillis(): invoked on an invalid result: getStatus()="
+ mStatus);
}
return mTimestamp;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mStatus);
if (mMac == null) {
dest.writeBoolean(false);
} else {
dest.writeBoolean(true);
mMac.writeToParcel(dest, flags);
}
if (mPeerHandle == null) {
dest.writeBoolean(false);
} else {
dest.writeBoolean(true);
dest.writeInt(mPeerHandle.peerId);
}
dest.writeInt(mDistanceMm);
dest.writeInt(mDistanceStdDevMm);
dest.writeInt(mRssi);
dest.writeInt(mNumAttemptedMeasurements);
dest.writeInt(mNumSuccessfulMeasurements);
dest.writeByteArray(mLci);
dest.writeByteArray(mLcr);
dest.writeParcelable(mResponderLocation, flags);
dest.writeLong(mTimestamp);
}
public static final @android.annotation.NonNull Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
@Override
public RangingResult[] newArray(int size) {
return new RangingResult[size];
}
@Override
public RangingResult createFromParcel(Parcel in) {
int status = in.readInt();
boolean macAddressPresent = in.readBoolean();
MacAddress mac = null;
if (macAddressPresent) {
mac = MacAddress.CREATOR.createFromParcel(in);
}
boolean peerHandlePresent = in.readBoolean();
PeerHandle peerHandle = null;
if (peerHandlePresent) {
peerHandle = new PeerHandle(in.readInt());
}
int distanceMm = in.readInt();
int distanceStdDevMm = in.readInt();
int rssi = in.readInt();
int numAttemptedMeasurements = in.readInt();
int numSuccessfulMeasurements = in.readInt();
byte[] lci = in.createByteArray();
byte[] lcr = in.createByteArray();
ResponderLocation responderLocation =
in.readParcelable(this.getClass().getClassLoader());
long timestamp = in.readLong();
if (peerHandlePresent) {
return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
responderLocation, timestamp);
} else {
return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
numAttemptedMeasurements, numSuccessfulMeasurements, lci, lcr,
responderLocation, timestamp);
}
}
};
/** @hide */
@Override
public String toString() {
return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
mMac).append(", peerHandle=").append(
mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(", distanceMm=").append(
mDistanceMm).append(", distanceStdDevMm=").append(mDistanceStdDevMm).append(
", rssi=").append(mRssi).append(", numAttemptedMeasurements=").append(
mNumAttemptedMeasurements).append(", numSuccessfulMeasurements=").append(
mNumSuccessfulMeasurements).append(", lci=").append(mLci).append(", lcr=").append(
mLcr).append(", responderLocation=").append(mResponderLocation)
.append(", timestamp=").append(mTimestamp).append("]").toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RangingResult)) {
return false;
}
RangingResult lhs = (RangingResult) o;
return mStatus == lhs.mStatus && Objects.equals(mMac, lhs.mMac) && Objects.equals(
mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
&& mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
&& mNumAttemptedMeasurements == lhs.mNumAttemptedMeasurements
&& mNumSuccessfulMeasurements == lhs.mNumSuccessfulMeasurements
&& Arrays.equals(mLci, lhs.mLci) && Arrays.equals(mLcr, lhs.mLcr)
&& mTimestamp == lhs.mTimestamp
&& Objects.equals(mResponderLocation, lhs.mResponderLocation);
}
@Override
public int hashCode() {
return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
mNumAttemptedMeasurements, mNumSuccessfulMeasurements, mLci, mLcr,
mResponderLocation, mTimestamp);
}
}