/* * 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.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.MacAddress; import android.net.wifi.OuiKeyedData; import android.net.wifi.ParcelUtil; import android.net.wifi.ScanResult; import android.net.wifi.aware.AttachCallback; import android.net.wifi.aware.DiscoverySessionCallback; import android.net.wifi.aware.IdentityChangedListener; import android.net.wifi.aware.PeerHandle; import android.net.wifi.aware.WifiAwareManager; import android.os.Build; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; import com.android.wifi.flags.Flags; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.StringJoiner; /** * Defines the ranging request to other devices. The ranging request is built using * {@link RangingRequest.Builder}. * A ranging request is executed using * {@link WifiRttManager#startRanging(RangingRequest, java.util.concurrent.Executor, RangingResultCallback)}. *

* The ranging request is a batch request - specifying a set of devices (specified using * {@link RangingRequest.Builder#addAccessPoint(ScanResult)} and * {@link RangingRequest.Builder#addAccessPoints(List)}). */ public final class RangingRequest implements Parcelable { private static final int MAX_PEERS = 10; private static final int DEFAULT_RTT_BURST_SIZE = 8; private static final int MIN_RTT_BURST_SIZE = 2; private static final int MAX_RTT_BURST_SIZE = 31; /** * Returns the maximum number of peers to range which can be specified in a single {@code * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g. * through {@link RangingRequest.Builder#addAccessPoint(ScanResult)} or * {@link RangingRequest.Builder#addAccessPoints(List)}. * * @return Maximum number of peers. */ public static int getMaxPeers() { return MAX_PEERS; } /** * Returns the default RTT burst size used to determine the average range. * * @return the RTT burst size used by default */ public static int getDefaultRttBurstSize() { return DEFAULT_RTT_BURST_SIZE; } /** * Returns the minimum RTT burst size that can be used to determine a average range. * * @return the minimum RTT burst size that can be used */ public static int getMinRttBurstSize() { return MIN_RTT_BURST_SIZE; } /** * Returns the minimum RTT burst size that can be used to determine a average range. * * @return the maximum RTT burst size that can be used */ public static int getMaxRttBurstSize() { return MAX_RTT_BURST_SIZE; } /** @hide */ public final List mRttPeers; /** @hide */ public final int mRttBurstSize; /** * List of {@link OuiKeyedData} providing vendor-specific configuration data. */ private @NonNull List mVendorData; /** @hide */ private RangingRequest(List rttPeers, int rttBurstSize, @NonNull List vendorData) { mRttPeers = rttPeers; mRttBurstSize = rttBurstSize; mVendorData = new ArrayList<>(vendorData); } /** * Returns the list of RTT capable responding peers. * * @return the list of RTT capable responding peers in a common system representation * * @hide */ @SystemApi @NonNull public List getRttResponders() { return mRttPeers; } /** * Returns the RTT burst size used to determine the average range. * * @return the RTT burst size used */ public int getRttBurstSize() { return mRttBurstSize; } /** * Return the vendor-provided configuration data, if it exists. See also {@link * Builder#setVendorData(List)} * * @return Vendor configuration data, or empty list if it does not exist. * @hide */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) @NonNull @SystemApi public List getVendorData() { if (!SdkLevel.isAtLeastV()) { throw new UnsupportedOperationException(); } return mVendorData; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeList(mRttPeers); dest.writeInt(mRttBurstSize); dest.writeList(mVendorData); } public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @Override public RangingRequest[] newArray(int size) { return new RangingRequest[size]; } @Override public RangingRequest createFromParcel(Parcel in) { return new RangingRequest(in.readArrayList(null), in.readInt(), ParcelUtil.readOuiKeyedDataList(in)); } }; /** @hide */ @Override public String toString() { StringJoiner sj = new StringJoiner(", ", "RangingRequest: mRttPeers=[", "]"); for (ResponderConfig rc : mRttPeers) { sj.add(rc.toString()); } sj.add("mRttBurstSize=" + mRttBurstSize); sj.add("mVendorData=" + mVendorData); return sj.toString(); } /** @hide */ public void enforceValidity(boolean awareSupported) { if (mRttPeers.size() > MAX_PEERS) { throw new IllegalArgumentException( "Ranging to too many peers requested. Use getMaxPeers() API to get limit."); } for (ResponderConfig peer: mRttPeers) { if (!peer.isValid(awareSupported)) { throw new IllegalArgumentException("Invalid Responder specification"); } } if (mRttBurstSize < getMinRttBurstSize() || mRttBurstSize > getMaxRttBurstSize()) { throw new IllegalArgumentException("RTT burst size is out of range"); } if (mVendorData == null) { throw new IllegalArgumentException("Vendor data must be non-null"); } } /** * Builder class used to construct {@link RangingRequest} objects. */ public static final class Builder { private List mRttPeers = new ArrayList<>(); private int mRttBurstSize = DEFAULT_RTT_BURST_SIZE; private @NonNull List mVendorData = Collections.emptyList(); /** * Set the RTT Burst size for the ranging request. *

* If not set, the default RTT burst size given by * {@link #getDefaultRttBurstSize()} is used to determine the default value. * If set, the value must be in the range {@link #getMinRttBurstSize()} and * {@link #getMaxRttBurstSize()} inclusively, or a * {@link java.lang.IllegalArgumentException} will be thrown. * * Note: RTT burst size is applicable to IEEE 802.11mc, and for one special case it is * also applicable to IEEE 802.11az to generate multiple NTB ranging requests per * measurement. It is applicable for IEEE 802.11az based ranging requests when MIMO is * not available, with the transmit and receive spatial streams between the initiator and * responder station is equal to 1. See * {@link RangingResult#get80211azNumberOfRxSpatialStreams()} and * {@link RangingResult#get80211azNumberOfTxSpatialStreams()}. * * @param rttBurstSize The number of FTM packets used to estimate a range. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder setRttBurstSize(int rttBurstSize) { if (rttBurstSize < MIN_RTT_BURST_SIZE || rttBurstSize > MAX_RTT_BURST_SIZE) { throw new IllegalArgumentException("RTT burst size out of range."); } mRttBurstSize = rttBurstSize; return this; } /** * Add the device specified by the {@link ScanResult} to the list of devices with * which to measure range. The total number of peers added to a request cannot exceed the * limit specified by {@link #getMaxPeers()}. *

* Two-sided Ranging will be performed if the local device and the AP support IEEE 802.11az * (non-trigger based ranging) or IEEE 802.11mc. AP capability is determined by the method * {@link ScanResult#is80211azNtbResponder()} or {@link ScanResult#is80211mcResponder()}. * * If both 11az and 11mc are supported by the local device and the AP, 11az non-trigger * based ranging will be performed. * * If two-sided ranging is not supported, one-sided RTT will be performed with no * correction for the AP packet turnaround time. * * @param apInfo Information about an Access Point (AP) obtained in a Scan Result. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder addAccessPoint(@NonNull ScanResult apInfo) { if (apInfo == null) { throw new IllegalArgumentException("Null ScanResult!"); } return addResponder(ResponderConfig.fromScanResult(apInfo)); } /** * Add the devices specified by the {@link ScanResult}s to the list of devices with * which to measure range. The total number of peers added to a request cannot exceed the * limit specified by {@link #getMaxPeers()}. *

* Two-sided Ranging will be performed if the local device and the AP support IEEE 802.11az * (non-trigger based ranging) or IEEE 802.11mc. AP capability is determined by the method * {@link ScanResult#is80211azNtbResponder()} or {@link ScanResult#is80211mcResponder()}. * * If both 11az and 11mc are supported by the local device and the AP, 11az non-trigger * based ranging will be performed. * * If two-sided ranging is not supported, one-sided RTT will be performed with no * correction for the AP packet turnaround time. * * @param apInfos Information about Access Points (APs) obtained in a Scan Result. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder addAccessPoints(@NonNull List apInfos) { if (apInfos == null) { throw new IllegalArgumentException("Null list of ScanResults!"); } for (ScanResult scanResult : apInfos) { addAccessPoint(scanResult); } return this; } /** * Add the Responder device specified by the {@link ResponderConfig} to the list of devices * with which to measure range. The total number of peers added to the request cannot exceed * the limit specified by {@link #getMaxPeers()}. *

* Two-sided Ranging will be performed if the local device and the AP support IEEE 802.11az * (non-trigger based ranging) or IEEE 802.11mc. AP capability is determined by the method * {@link ScanResult#is80211azNtbResponder()} or {@link ScanResult#is80211mcResponder()}. * * If both 11az and 11mc are supported by the local device and the AP, 11az non-trigger * based ranging will be performed. * * If two-sided ranging is not supported, one-sided RTT will be performed with no * correction for the AP packet turnaround time. * * @param responder Information on the RTT Responder. * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder addResponder(@NonNull ResponderConfig responder) { if (responder == null) { throw new IllegalArgumentException("Null Responder!"); } mRttPeers.add(responder); return this; } /** * Add the devices specified by the {@link ResponderConfig}s to the list of devices with * which to measure range. The total number of peers added to a request cannot exceed the * limit specified by {@link #getMaxPeers()}. *

* Two-sided Ranging will be performed if the local device and the AP support IEEE 802.11az * (non-trigger based ranging) or IEEE 802.11mc. AP capability is determined by the method * {@link ScanResult#is80211azNtbResponder()} or {@link ScanResult#is80211mcResponder()}. * * If both 11az and 11mc are supported by the local device and the AP, 11az non-trigger * based ranging will be performed. * * If two-sided ranging is not supported, one-sided RTT will be performed with no * correction for the AP packet turnaround time. * * @param responders Information representing the set of access points to be ranged * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder addResponders(@NonNull List responders) { if (responders == null) { throw new IllegalArgumentException("Null list of Responders"); } for (ResponderConfig responder : responders) { addResponder(responder); } return this; } /** * Add the non-802.11mc and non-802.11az capable device specified by the {@link ScanResult} * to the list of devices with which to measure range. The total number of peers added to a * request cannot exceed the limit specified by {@link #getMaxPeers()}. *

* Accurate ranging cannot be supported if the Access Point does not support IEEE 802.11mc * and IEEE 802.11az, and instead an alternate protocol called one-sided RTT will be used * with lower accuracy. Use {@link ScanResult#is80211mcResponder()} to verify the Access * Point(s) are not 802.11mc capable. Use {@link ScanResult#is80211azNtbResponder()} ()} to * verify the Access Point)s) are not 802.11az capable. *

* One-sided RTT does not subtract the RTT turnaround time at the Access Point, which can * add hundreds of meters to the estimate. With experimentation, it is possible to use this * information to make a statistical estimate of the range by taking multiple measurements * to several Access Points and normalizing the result. For some applications this can be * used to improve range estimates based on Receive Signal Strength Indication (RSSI), but * will not be as accurate as IEEE 802.11mc (two-sided RTT). *

* Note: one-sided RTT should only be used if you are very familiar with statistical * estimation techniques. * * @param apInfo Information about an Access Point (AP) obtained in a Scan Result * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder addNon80211mcCapableAccessPoint(@NonNull ScanResult apInfo) { if (apInfo == null) { throw new IllegalArgumentException("Null ScanResult!"); } if (apInfo.is80211mcResponder() || apInfo.is80211azNtbResponder()) { throw new IllegalArgumentException( "AP supports the 802.11mc or 8022.11az protocol."); } return addResponder(ResponderConfig.fromScanResult(apInfo)); } /** * Add the non-802.11mc and non-802.11az capable devices specified by the {@link ScanResult} * to the list of devices with which to measure range. The total number of peers added to a * request cannot exceed the limit specified by {@link #getMaxPeers()}. *

* Accurate ranging cannot be supported if the Access Point does not support IEEE 802.11mc * and IEEE 802.11az, and instead an alternate protocol called one-sided RTT will be used * with lower accuracy. Use {@link ScanResult#is80211mcResponder()} to verify the Access * Point(s) are not 802.11mc capable. Use {@link ScanResult#is80211azNtbResponder()} ()} to * verify the Access Point(s) are not 802.11az capable. *

* One-sided RTT does not subtract the RTT turnaround time at the Access Point, which can * add hundreds of meters to the estimate. With experimentation, it is possible to use this * information to make a statistical estimate of the range by taking multiple measurements * to several Access Points and normalizing the result. For some applications this can be * used to improve range estimates based on Receive Signal Strength Indication (RSSI), but * will not be as accurate as IEEE 802.11mc (two-sided RTT). *

* Note: one-sided RTT should only be used if you are very familiar with statistical * estimation techniques. * * @param apInfos Information about Access Points (APs) obtained in a Scan Result. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder addNon80211mcCapableAccessPoints(@NonNull List apInfos) { if (apInfos == null) { throw new IllegalArgumentException("Null list of ScanResults!"); } for (ScanResult scanResult : apInfos) { if (scanResult.is80211mcResponder() || scanResult.is80211azNtbResponder()) { throw new IllegalArgumentException( "At least one AP supports the 802.11mc or 802.11az protocol."); } addAccessPoint(scanResult); } return this; } /** * Add the device specified by the {@code peerMacAddress} to the list of devices with * which to measure range. *

* The MAC address may be obtained out-of-band from a peer Wi-Fi Aware device. A Wi-Fi * Aware device may obtain its MAC address using the {@link IdentityChangedListener} * provided to * {@link WifiAwareManager#attach(AttachCallback, IdentityChangedListener, Handler)}. *

* Note: in order to use this API the device must support Wi-Fi Aware * {@link android.net.wifi.aware}. The peer device which is being ranged to must be * configured to publish a service (with any name) with: *

  • Type {@link android.net.wifi.aware.PublishConfig#PUBLISH_TYPE_UNSOLICITED}. *
  • Ranging enabled * {@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)}. * * @param peerMacAddress The MAC address of the Wi-Fi Aware peer. * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. */ public Builder addWifiAwarePeer(@NonNull MacAddress peerMacAddress) { if (peerMacAddress == null) { throw new IllegalArgumentException("Null peer MAC address"); } return addResponder( ResponderConfig.fromWifiAwarePeerMacAddressWithDefaults(peerMacAddress)); } /** * Add a device specified by a {@link PeerHandle} to the list of devices with which to * measure range. *

    * The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g. * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}. *

    * Note: in order to use this API the device must support Wi-Fi Aware * {@link android.net.wifi.aware}. The requesting device can be either publisher or * subscriber in a discovery session. For both requesting device and peer device ranging * must be enabled on the discovery session: *

  • {@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)} for * publisher.
  • *
  • Either {@link android.net.wifi.aware.SubscribeConfig.Builder#setMinDistanceMm(int)} * or {@link android.net.wifi.aware.SubscribeConfig.Builder#setMaxDistanceMm(int)} must be * set to enable ranging on subscriber
  • * * @param peerHandle The peer handler of the peer Wi-Fi Aware device. * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. */ public Builder addWifiAwarePeer(@NonNull PeerHandle peerHandle) { if (peerHandle == null) { throw new IllegalArgumentException("Null peer handler (identifier)"); } return addResponder(ResponderConfig.fromWifiAwarePeerHandleWithDefaults(peerHandle)); } /** * Set additional vendor-provided configuration data. * * @param vendorData List of {@link OuiKeyedData} containing the vendor-provided * configuration data. Note that multiple elements with the same OUI are allowed. * @return Builder for chaining. * @hide */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) @NonNull @SystemApi public Builder setVendorData(@NonNull List vendorData) { if (!SdkLevel.isAtLeastV()) { throw new UnsupportedOperationException(); } if (vendorData == null) { throw new IllegalArgumentException("setVendorData received a null value"); } mVendorData = vendorData; return this; } /** * Build {@link RangingRequest} given the current configurations made on the * builder. */ public RangingRequest build() { return new RangingRequest(mRttPeers, mRttBurstSize, mVendorData); } } @Override public boolean equals(@Nullable Object o) { if (this == o) { return true; } if (!(o instanceof RangingRequest)) { return false; } RangingRequest lhs = (RangingRequest) o; return mRttPeers.size() == lhs.mRttPeers.size() && mRttPeers.containsAll(lhs.mRttPeers) && mRttBurstSize == lhs.mRttBurstSize && Objects.equals(mVendorData, lhs.mVendorData); } @Override public int hashCode() { return Objects.hash(mRttPeers, mRttBurstSize, mVendorData); } }