content: /*
   * 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);
      }
  }
  