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.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.net.MacAddress;
23 import android.net.wifi.ScanResult;
24 import android.net.wifi.aware.AttachCallback;
25 import android.net.wifi.aware.DiscoverySessionCallback;
26 import android.net.wifi.aware.IdentityChangedListener;
27 import android.net.wifi.aware.PeerHandle;
28 import android.net.wifi.aware.WifiAwareManager;
29 import android.os.Handler;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.Objects;
36 import java.util.StringJoiner;
37 
38 /**
39  * Defines the ranging request to other devices. The ranging request is built using
40  * {@link RangingRequest.Builder}.
41  * A ranging request is executed using
42  * {@link WifiRttManager#startRanging(RangingRequest, java.util.concurrent.Executor, RangingResultCallback)}.
43  * <p>
44  * The ranging request is a batch request - specifying a set of devices (specified using
45  * {@link RangingRequest.Builder#addAccessPoint(ScanResult)} and
46  * {@link RangingRequest.Builder#addAccessPoints(List)}).
47  */
48 public final class RangingRequest implements Parcelable {
49     private static final int MAX_PEERS = 10;
50     private static final int DEFAULT_RTT_BURST_SIZE = 8;
51     private static final int MIN_RTT_BURST_SIZE = 2;
52     private static final int MAX_RTT_BURST_SIZE = 31;
53 
54     /**
55      * Returns the maximum number of peers to range which can be specified in a single {@code
56      * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
57      * through {@link RangingRequest.Builder#addAccessPoint(ScanResult)} or
58      * {@link RangingRequest.Builder#addAccessPoints(List)}.
59      *
60      * @return Maximum number of peers.
61      */
getMaxPeers()62     public static int getMaxPeers() {
63         return MAX_PEERS;
64     }
65 
66     /**
67      * Returns the default RTT burst size used to determine the average range.
68      *
69      * @return the RTT burst size used by default
70      */
getDefaultRttBurstSize()71     public static int getDefaultRttBurstSize() {
72         return DEFAULT_RTT_BURST_SIZE;
73     }
74 
75     /**
76      * Returns the minimum RTT burst size that can be used to determine a average range.
77      *
78      * @return the minimum RTT burst size that can be used
79      */
getMinRttBurstSize()80     public static int getMinRttBurstSize() {
81         return MIN_RTT_BURST_SIZE;
82     }
83 
84     /**
85      * Returns the minimum RTT burst size that can be used to determine a average range.
86      *
87      * @return the maximum RTT burst size that can be used
88      */
getMaxRttBurstSize()89     public static int getMaxRttBurstSize() {
90         return MAX_RTT_BURST_SIZE;
91     }
92 
93     /** @hide */
94     public final List<ResponderConfig> mRttPeers;
95 
96     /** @hide */
97     public final int mRttBurstSize;
98 
99     /** @hide */
RangingRequest(List<ResponderConfig> rttPeers, int rttBurstSize)100     private RangingRequest(List<ResponderConfig> rttPeers, int rttBurstSize) {
101         mRttPeers = rttPeers;
102         mRttBurstSize = rttBurstSize;
103     }
104 
105     /**
106      * Returns the list of RTT capable responding peers.
107      *
108      * @return the list of RTT capable responding peers in a common system representation
109      *
110      * @hide
111      */
112     @SystemApi
113     @NonNull
getRttResponders()114     public List<ResponderConfig> getRttResponders() {
115         return mRttPeers;
116     }
117 
118     /**
119      * Returns the RTT burst size used to determine the average range.
120      *
121      * @return the RTT burst size used
122      */
getRttBurstSize()123     public int getRttBurstSize() {
124         return mRttBurstSize;
125     }
126 
127     @Override
describeContents()128     public int describeContents() {
129         return 0;
130     }
131 
132     @Override
writeToParcel(Parcel dest, int flags)133     public void writeToParcel(Parcel dest, int flags) {
134         dest.writeList(mRttPeers);
135         dest.writeInt(mRttBurstSize);
136     }
137 
138     public static final @android.annotation.NonNull Creator<RangingRequest> CREATOR = new Creator<RangingRequest>() {
139         @Override
140         public RangingRequest[] newArray(int size) {
141             return new RangingRequest[size];
142         }
143 
144         @Override
145         public RangingRequest createFromParcel(Parcel in) {
146             return new RangingRequest(in.readArrayList(null), in.readInt());
147         }
148     };
149 
150     /** @hide */
151     @Override
toString()152     public String toString() {
153         StringJoiner sj = new StringJoiner(", ", "RangingRequest: mRttPeers=[", "]");
154         for (ResponderConfig rc : mRttPeers) {
155             sj.add(rc.toString());
156         }
157         return sj.toString();
158     }
159 
160     /** @hide */
enforceValidity(boolean awareSupported)161     public void enforceValidity(boolean awareSupported) {
162         if (mRttPeers.size() > MAX_PEERS) {
163             throw new IllegalArgumentException(
164                     "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
165         }
166         for (ResponderConfig peer: mRttPeers) {
167             if (!peer.isValid(awareSupported)) {
168                 throw new IllegalArgumentException("Invalid Responder specification");
169             }
170         }
171         if (mRttBurstSize < getMinRttBurstSize() || mRttBurstSize > getMaxRttBurstSize()) {
172             throw new IllegalArgumentException("RTT burst size is out of range");
173         }
174     }
175 
176     /**
177      * Builder class used to construct {@link RangingRequest} objects.
178      */
179     public static final class Builder {
180         private List<ResponderConfig> mRttPeers = new ArrayList<>();
181         private int mRttBurstSize = DEFAULT_RTT_BURST_SIZE;
182 
183         /**
184          * Set the RTT Burst size for the ranging request.
185          * <p>
186          * If not set, the default RTT burst size given by
187          * {@link #getDefaultRttBurstSize()} is used to determine the default value.
188          * If set, the value must be in the range {@link #getMinRttBurstSize()} and
189          * {@link #getMaxRttBurstSize()} inclusively, or a
190          * {@link java.lang.IllegalArgumentException} will be thrown.
191          *
192          * @param rttBurstSize The number of FTM packets used to estimate a range.
193          * @return The builder to facilitate chaining
194          * {@code builder.setXXX(..).setXXX(..)}.
195          */
196         @NonNull
setRttBurstSize(int rttBurstSize)197         public Builder setRttBurstSize(int rttBurstSize) {
198             if (rttBurstSize < MIN_RTT_BURST_SIZE || rttBurstSize > MAX_RTT_BURST_SIZE) {
199                 throw new IllegalArgumentException("RTT burst size out of range.");
200             }
201             mRttBurstSize = rttBurstSize;
202             return this;
203         }
204 
205         /**
206          * Add the device specified by the {@link ScanResult} to the list of devices with
207          * which to measure range. The total number of peers added to a request cannot exceed the
208          * limit specified by {@link #getMaxPeers()}.
209          * <p>
210          * Ranging will only be supported if the Access Point supports IEEE 802.11mc, also known as
211          * two-sided RTT. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point's
212          * capabilities. If not supported the result status will be
213          * {@link RangingResult#STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC}.
214          *
215          * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
216          * @return The builder to facilitate chaining
217          *         {@code builder.setXXX(..).setXXX(..)}.
218          */
addAccessPoint(@onNull ScanResult apInfo)219         public Builder addAccessPoint(@NonNull ScanResult apInfo) {
220             if (apInfo == null) {
221                 throw new IllegalArgumentException("Null ScanResult!");
222             }
223             return addResponder(ResponderConfig.fromScanResult(apInfo));
224         }
225 
226         /**
227          * Add the devices specified by the {@link ScanResult}s to the list of devices with
228          * which to measure range. The total number of peers added to a request cannot exceed the
229          * limit specified by {@link #getMaxPeers()}.
230          * <p>
231          * Ranging will only be supported if the Access Point supports IEEE 802.11mc, also known as
232          * two-sided RTT. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point's
233          * capabilities. If not supported, the result status will be
234          * {@link RangingResult#STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC}.
235          *
236          * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
237          * @return The builder to facilitate chaining
238          *         {@code builder.setXXX(..).setXXX(..)}.
239          */
addAccessPoints(@onNull List<ScanResult> apInfos)240         public Builder addAccessPoints(@NonNull List<ScanResult> apInfos) {
241             if (apInfos == null) {
242                 throw new IllegalArgumentException("Null list of ScanResults!");
243             }
244             for (ScanResult scanResult : apInfos) {
245                 addAccessPoint(scanResult);
246             }
247             return this;
248         }
249 
250         /**
251          * Add the non-802.11mc capable device specified by the {@link ScanResult} to the list of
252          * devices with which to measure range. The total number of peers added to a request cannot
253          * exceed the limit specified by {@link #getMaxPeers()}.
254          * <p>
255          * Accurate ranging cannot be supported if the Access Point does not support IEEE 802.11mc,
256          * and instead an alternate protocol called one-sided RTT will be used with lower
257          * accuracy. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point)s) are
258          * not 802.11mc capable.
259          * <p>
260          * One-sided RTT does not subtract the RTT turnaround time at the Access Point, which can
261          * add hundreds of meters to the estimate. With experimentation it is possible to use this
262          * information to make a statistical estimate of the range by taking multiple measurements
263          * to several Access Points and normalizing the result. For some applications this can be
264          * used to improve range estimates based on Receive Signal Strength Indication (RSSI), but
265          * will not be as accurate as IEEE 802.11mc (two-sided RTT).
266          * <p>
267          * Note: one-sided RTT should only be used if you are very familiar with statistical
268          * estimation techniques.
269          *
270          * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
271          * @return The builder to facilitate chaining
272          *         {@code builder.setXXX(..).setXXX(..)}.
273          */
274         @NonNull
addNon80211mcCapableAccessPoint(@onNull ScanResult apInfo)275         public Builder addNon80211mcCapableAccessPoint(@NonNull ScanResult apInfo) {
276             if (apInfo == null) {
277                 throw new IllegalArgumentException("Null ScanResult!");
278             }
279             if (apInfo.is80211mcResponder()) {
280                 throw new IllegalArgumentException("AP supports the 802.11mc protocol.");
281             }
282             return addResponder(ResponderConfig.fromScanResult(apInfo));
283         }
284 
285         /**
286          * Add the non-802.11mc capable devices specified by the {@link ScanResult} to the list of
287          * devices with which to measure range. The total number of peers added to a request cannot
288          * exceed the limit specified by {@link #getMaxPeers()}.
289          * <p>
290          * Accurate ranging cannot be supported if the Access Point does not support IEEE 802.11mc,
291          * and instead an alternate protocol called one-sided RTT will be used with lower
292          * accuracy. Use {@link ScanResult#is80211mcResponder()} to verify the Access Point)s) are
293          * not 802.11mc capable.
294          * <p>
295          * One-sided RTT does not subtract the RTT turnaround time at the Access Point, which can
296          * add hundreds of meters to the estimate. With experimentation it is possible to use this
297          * information to make a statistical estimate of the range by taking multiple measurements
298          * to several Access Points and normalizing the result. For some applications this can be
299          * used to improve range estimates based on Receive Signal Strength Indication (RSSI), but
300          * will not be as accurate as IEEE 802.11mc (two-sided RTT).
301          * <p>
302          * Note: one-sided RTT should only be used if you are very familiar with statistical
303          * estimation techniques.
304          *
305          * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
306          * @return The builder to facilitate chaining
307          *         {@code builder.setXXX(..).setXXX(..)}.
308          */
309         @NonNull
addNon80211mcCapableAccessPoints(@onNull List<ScanResult> apInfos)310         public Builder addNon80211mcCapableAccessPoints(@NonNull List<ScanResult> apInfos) {
311             if (apInfos == null) {
312                 throw new IllegalArgumentException("Null list of ScanResults!");
313             }
314             for (ScanResult scanResult : apInfos) {
315                 if (scanResult.is80211mcResponder()) {
316                     throw new IllegalArgumentException(
317                             "At least one AP supports the 802.11mc protocol.");
318                 }
319                 addAccessPoint(scanResult);
320             }
321             return this;
322         }
323 
324         /**
325          * Add the device specified by the {@code peerMacAddress} to the list of devices with
326          * which to measure range.
327          * <p>
328          * The MAC address may be obtained out-of-band from a peer Wi-Fi Aware device. A Wi-Fi
329          * Aware device may obtain its MAC address using the {@link IdentityChangedListener}
330          * provided to
331          * {@link WifiAwareManager#attach(AttachCallback, IdentityChangedListener, Handler)}.
332          * <p>
333          * Note: in order to use this API the device must support Wi-Fi Aware
334          * {@link android.net.wifi.aware}. The peer device which is being ranged to must be
335          * configured to publish a service (with any name) with:
336          * <li>Type {@link android.net.wifi.aware.PublishConfig#PUBLISH_TYPE_UNSOLICITED}.
337          * <li>Ranging enabled
338          * {@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)}.
339          *
340          * @param peerMacAddress The MAC address of the Wi-Fi Aware peer.
341          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
342          */
addWifiAwarePeer(@onNull MacAddress peerMacAddress)343         public Builder addWifiAwarePeer(@NonNull MacAddress peerMacAddress) {
344             if (peerMacAddress == null) {
345                 throw new IllegalArgumentException("Null peer MAC address");
346             }
347             return addResponder(
348                     ResponderConfig.fromWifiAwarePeerMacAddressWithDefaults(peerMacAddress));
349         }
350 
351         /**
352          * Add a device specified by a {@link PeerHandle} to the list of devices with which to
353          * measure range.
354          * <p>
355          * The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g.
356          * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}.
357          * <p>
358          * Note: in order to use this API the device must support Wi-Fi Aware
359          * {@link android.net.wifi.aware}. The requesting device can be either publisher or
360          * subscriber in a discovery session. For both requesting device and peer device ranging
361          * must be enabled on the discovery session:
362          * <li>{@link android.net.wifi.aware.PublishConfig.Builder#setRangingEnabled(boolean)} for
363          * publisher.</li>
364          * <li>Either {@link android.net.wifi.aware.SubscribeConfig.Builder#setMinDistanceMm(int)}
365          * or {@link android.net.wifi.aware.SubscribeConfig.Builder#setMaxDistanceMm(int)} must be
366          * set to enable ranging on subscriber </li>
367          *
368          * @param peerHandle The peer handler of the peer Wi-Fi Aware device.
369          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
370          */
addWifiAwarePeer(@onNull PeerHandle peerHandle)371         public Builder addWifiAwarePeer(@NonNull PeerHandle peerHandle) {
372             if (peerHandle == null) {
373                 throw new IllegalArgumentException("Null peer handler (identifier)");
374             }
375 
376             return addResponder(ResponderConfig.fromWifiAwarePeerHandleWithDefaults(peerHandle));
377         }
378 
379         /**
380          * Add the Responder device specified by the {@link ResponderConfig} to the list of devices
381          * with which to measure range. The total number of peers added to the request cannot exceed
382          * the limit specified by {@link #getMaxPeers()}.
383          *
384          * @param responder Information on the RTT Responder.
385          * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
386          *
387          * @hide
388          */
389         @SystemApi
addResponder(@onNull ResponderConfig responder)390         public Builder addResponder(@NonNull ResponderConfig responder) {
391             if (responder == null) {
392                 throw new IllegalArgumentException("Null Responder!");
393             }
394 
395             mRttPeers.add(responder);
396             return this;
397         }
398 
399         /**
400          * Build {@link RangingRequest} given the current configurations made on the
401          * builder.
402          */
build()403         public RangingRequest build() {
404             return new RangingRequest(mRttPeers, mRttBurstSize);
405         }
406     }
407 
408     @Override
equals(@ullable Object o)409     public boolean equals(@Nullable Object o) {
410         if (this == o) {
411             return true;
412         }
413 
414         if (!(o instanceof RangingRequest)) {
415             return false;
416         }
417 
418         RangingRequest lhs = (RangingRequest) o;
419 
420         return mRttPeers.size() == lhs.mRttPeers.size()
421                 && mRttPeers.containsAll(lhs.mRttPeers)
422                 && mRttBurstSize == lhs.mRttBurstSize;
423     }
424 
425     @Override
hashCode()426     public int hashCode() {
427         return Objects.hash(mRttPeers, mRttBurstSize);
428     }
429 }
430