1 /*
2  * Copyright (C) 2016 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.aware;
18 
19 import static android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION;
20 
21 import android.annotation.FlaggedApi;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SystemApi;
27 import android.net.wifi.OuiKeyedData;
28 import android.net.wifi.ParcelUtil;
29 import android.net.wifi.ScanResult;
30 import android.net.wifi.WifiScanner;
31 import android.net.wifi.util.HexEncoding;
32 import android.os.Build;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 
36 import androidx.annotation.RequiresApi;
37 
38 import com.android.modules.utils.build.SdkLevel;
39 import com.android.wifi.flags.Flags;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 import java.nio.charset.StandardCharsets;
44 import java.util.Arrays;
45 import java.util.Collections;
46 import java.util.List;
47 import java.util.Objects;
48 
49 /**
50  * Defines the configuration of an Aware subscribe session. Built using
51  * {@link SubscribeConfig.Builder}. Subscribe is done using
52  * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
53  * android.os.Handler)} or
54  * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}.
55  */
56 public final class SubscribeConfig implements Parcelable {
57     /** @hide */
58     @IntDef({
59             SUBSCRIBE_TYPE_PASSIVE, SUBSCRIBE_TYPE_ACTIVE })
60     @Retention(RetentionPolicy.SOURCE)
61     public @interface SubscribeTypes {
62     }
63 
64     /**
65      * Defines a passive subscribe session - a subscribe session where
66      * subscribe packets are not transmitted over-the-air and the device listens
67      * and matches to transmitted publish packets. Configuration is done using
68      * {@link SubscribeConfig.Builder#setSubscribeType(int)}.
69      */
70     public static final int SUBSCRIBE_TYPE_PASSIVE = 0;
71 
72     /**
73      * Defines an active subscribe session - a subscribe session where
74      * subscribe packets are transmitted over-the-air. Configuration is done
75      * using {@link SubscribeConfig.Builder#setSubscribeType(int)}.
76      */
77     public static final int SUBSCRIBE_TYPE_ACTIVE = 1;
78 
79     /** @hide */
80     public final byte[] mServiceName;
81 
82     /** @hide */
83     public final byte[] mServiceSpecificInfo;
84 
85     /** @hide */
86     public final byte[] mMatchFilter;
87 
88     /** @hide */
89     public final int mSubscribeType;
90 
91     /** @hide */
92     public final int mTtlSec;
93 
94     /** @hide */
95     public final boolean mEnableTerminateNotification;
96 
97     /** @hide */
98     public final boolean mMinDistanceMmSet;
99 
100     /** @hide */
101     public final int mMinDistanceMm;
102 
103     /** @hide */
104     public final boolean mMaxDistanceMmSet;
105 
106     /** @hide */
107     public final int mMaxDistanceMm;
108 
109     private final boolean mEnableInstantMode;
110 
111     private final int mBand;
112 
113     private final AwarePairingConfig mPairingConfig;
114 
115     private final boolean mIsSuspendable;
116     private final List<OuiKeyedData> mVendorData;
117 
118     /** @hide */
SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, int subscribeType, int ttlSec, boolean enableTerminateNotification, boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet, int maxDistanceMm, boolean enableInstantMode, @WifiScanner.WifiBand int band, AwarePairingConfig pairingConfig, boolean isSuspendable, @NonNull List<OuiKeyedData> vendorData)119     public SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
120             int subscribeType, int ttlSec, boolean enableTerminateNotification,
121             boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet,
122             int maxDistanceMm, boolean enableInstantMode, @WifiScanner.WifiBand int band,
123             AwarePairingConfig pairingConfig, boolean isSuspendable,
124             @NonNull List<OuiKeyedData> vendorData) {
125         mServiceName = serviceName;
126         mServiceSpecificInfo = serviceSpecificInfo;
127         mMatchFilter = matchFilter;
128         mSubscribeType = subscribeType;
129         mTtlSec = ttlSec;
130         mEnableTerminateNotification = enableTerminateNotification;
131         mMinDistanceMm = minDistanceMm;
132         mMinDistanceMmSet = minDistanceMmSet;
133         mMaxDistanceMm = maxDistanceMm;
134         mMaxDistanceMmSet = maxDistanceMmSet;
135         mEnableInstantMode = enableInstantMode;
136         mBand = band;
137         mPairingConfig = pairingConfig;
138         mIsSuspendable = isSuspendable;
139         mVendorData = vendorData;
140     }
141 
142     @Override
toString()143     public String toString() {
144         return "SubscribeConfig [mServiceName='" + (mServiceName == null ? "<null>"
145                 : String.valueOf(HexEncoding.encode(mServiceName))) + ", mServiceName.length=" + (
146                 mServiceName == null ? 0 : mServiceName.length) + ", mServiceSpecificInfo='" + (
147                 (mServiceSpecificInfo == null) ? "<null>" : String.valueOf(
148                         HexEncoding.encode(mServiceSpecificInfo)))
149                 + ", mServiceSpecificInfo.length=" + (mServiceSpecificInfo == null ? 0
150                 : mServiceSpecificInfo.length) + ", mMatchFilter="
151                 + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
152                 + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
153                 + ", mSubscribeType=" + mSubscribeType + ", mTtlSec=" + mTtlSec
154                 + ", mEnableTerminateNotification=" + mEnableTerminateNotification
155                 + ", mMinDistanceMm=" + mMinDistanceMm
156                 + ", mMinDistanceMmSet=" + mMinDistanceMmSet
157                 + ", mMaxDistanceMm=" + mMaxDistanceMm
158                 + ", mMaxDistanceMmSet=" + mMaxDistanceMmSet + "]"
159                 + ", mEnableInstantMode=" + mEnableInstantMode
160                 + ", mBand=" + mBand
161                 + ", mPairingConfig" + mPairingConfig
162                 + ", mIsSuspendable=" + mIsSuspendable
163                 + ", mVendorData=" + mVendorData + "]";
164     }
165 
166     @Override
describeContents()167     public int describeContents() {
168         return 0;
169     }
170 
171     @Override
writeToParcel(Parcel dest, int flags)172     public void writeToParcel(Parcel dest, int flags) {
173         dest.writeByteArray(mServiceName);
174         dest.writeByteArray(mServiceSpecificInfo);
175         dest.writeByteArray(mMatchFilter);
176         dest.writeInt(mSubscribeType);
177         dest.writeInt(mTtlSec);
178         dest.writeInt(mEnableTerminateNotification ? 1 : 0);
179         dest.writeInt(mMinDistanceMm);
180         dest.writeInt(mMinDistanceMmSet ? 1 : 0);
181         dest.writeInt(mMaxDistanceMm);
182         dest.writeInt(mMaxDistanceMmSet ? 1 : 0);
183         dest.writeBoolean(mEnableInstantMode);
184         dest.writeInt(mBand);
185         dest.writeParcelable(mPairingConfig, flags);
186         dest.writeBoolean(mIsSuspendable);
187         dest.writeList(mVendorData);
188     }
189 
190     @NonNull
191     public static final Creator<SubscribeConfig> CREATOR = new Creator<>() {
192         @Override
193         public SubscribeConfig[] newArray(int size) {
194             return new SubscribeConfig[size];
195         }
196 
197         @Override
198         public SubscribeConfig createFromParcel(Parcel in) {
199             byte[] serviceName = in.createByteArray();
200             byte[] ssi = in.createByteArray();
201             byte[] matchFilter = in.createByteArray();
202             int subscribeType = in.readInt();
203             int ttlSec = in.readInt();
204             boolean enableTerminateNotification = in.readInt() != 0;
205             int minDistanceMm = in.readInt();
206             boolean minDistanceMmSet = in.readInt() != 0;
207             int maxDistanceMm = in.readInt();
208             boolean maxDistanceMmSet = in.readInt() != 0;
209             boolean enableInstantMode = in.readBoolean();
210             int band = in.readInt();
211             AwarePairingConfig pairingConfig = in.readParcelable(
212                     AwarePairingConfig.class.getClassLoader());
213             boolean isSuspendable = in.readBoolean();
214             List<OuiKeyedData> vendorData = ParcelUtil.readOuiKeyedDataList(in);
215 
216             return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, ttlSec,
217                     enableTerminateNotification, minDistanceMmSet, minDistanceMm, maxDistanceMmSet,
218                     maxDistanceMm, enableInstantMode, band, pairingConfig, isSuspendable,
219                     vendorData);
220         }
221     };
222 
223     @Override
equals(Object o)224     public boolean equals(Object o) {
225         if (this == o) {
226             return true;
227         }
228 
229         if (!(o instanceof SubscribeConfig)) {
230             return false;
231         }
232 
233         SubscribeConfig lhs = (SubscribeConfig) o;
234 
235         if (!(Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(
236                 mServiceSpecificInfo, lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter,
237                 lhs.mMatchFilter) && mSubscribeType == lhs.mSubscribeType && mTtlSec == lhs.mTtlSec
238                 && mEnableTerminateNotification == lhs.mEnableTerminateNotification
239                 && mMinDistanceMmSet == lhs.mMinDistanceMmSet
240                 && mMaxDistanceMmSet == lhs.mMaxDistanceMmSet
241                 && mEnableInstantMode == lhs.mEnableInstantMode
242                 && mBand == lhs.mBand
243                 && mIsSuspendable == lhs.mIsSuspendable
244                 && Objects.equals(mVendorData, lhs.mVendorData))) {
245             return false;
246         }
247 
248         if (mMinDistanceMmSet && mMinDistanceMm != lhs.mMinDistanceMm) {
249             return false;
250         }
251 
252         if (mMaxDistanceMmSet && mMaxDistanceMm != lhs.mMaxDistanceMm) {
253             return false;
254         }
255 
256         return true;
257     }
258 
259     @Override
hashCode()260     public int hashCode() {
261         int result = Objects.hash(Arrays.hashCode(mServiceName),
262                 Arrays.hashCode(mServiceSpecificInfo), Arrays.hashCode(mMatchFilter),
263                 mSubscribeType, mTtlSec, mEnableTerminateNotification, mMinDistanceMmSet,
264                 mMaxDistanceMmSet, mEnableInstantMode, mBand, mIsSuspendable, mVendorData);
265 
266         if (mMinDistanceMmSet) {
267             result = Objects.hash(result, mMinDistanceMm);
268         }
269         if (mMaxDistanceMmSet) {
270             result = Objects.hash(result, mMaxDistanceMm);
271         }
272 
273         return result;
274     }
275 
276     /**
277      * Verifies that the contents of the SubscribeConfig are valid. Otherwise
278      * throws an IllegalArgumentException.
279      *
280      * @hide
281      */
assertValid(Characteristics characteristics, boolean rttSupported)282     public void assertValid(Characteristics characteristics, boolean rttSupported)
283             throws IllegalArgumentException {
284         WifiAwareUtils.validateServiceName(mServiceName);
285 
286         if (!TlvBufferUtils.isValid(mMatchFilter, 0, 1)) {
287             throw new IllegalArgumentException(
288                     "Invalid matchFilter configuration - LV fields do not match up to length");
289         }
290         if (mSubscribeType < SUBSCRIBE_TYPE_PASSIVE || mSubscribeType > SUBSCRIBE_TYPE_ACTIVE) {
291             throw new IllegalArgumentException("Invalid subscribeType - " + mSubscribeType);
292         }
293         if (mTtlSec < 0) {
294             throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
295         }
296 
297         if (characteristics != null) {
298             int maxServiceNameLength = characteristics.getMaxServiceNameLength();
299             if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) {
300                 throw new IllegalArgumentException(
301                         "Service name longer than supported by device characteristics");
302             }
303             int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength();
304             if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null
305                     && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) {
306                 throw new IllegalArgumentException(
307                         "Service specific info longer than supported by device characteristics");
308             }
309             int maxMatchFilterLength = characteristics.getMaxMatchFilterLength();
310             if (maxMatchFilterLength != 0 && mMatchFilter != null
311                     && mMatchFilter.length > maxMatchFilterLength) {
312                 throw new IllegalArgumentException(
313                         "Match filter longer than supported by device characteristics");
314             }
315             if (mEnableInstantMode) {
316                 if (SdkLevel.isAtLeastT()
317                         && characteristics.isInstantCommunicationModeSupported()) {
318                     // Valid to use instant communication mode
319                 } else {
320                     throw new IllegalArgumentException("instant mode is not supported");
321                 }
322             }
323             if (mIsSuspendable && !characteristics.isSuspensionSupported()) {
324                 throw new IllegalArgumentException("Aware Suspension is not supported");
325             }
326             if (mPairingConfig != null && !characteristics.isAwarePairingSupported()) {
327                 throw new IllegalArgumentException("Aware Pairing is not supported");
328             }
329         }
330 
331         if (mMinDistanceMmSet && mMinDistanceMm < 0) {
332             throw new IllegalArgumentException("Minimum distance must be non-negative");
333         }
334         if (mMaxDistanceMmSet && mMaxDistanceMm < 0) {
335             throw new IllegalArgumentException("Maximum distance must be non-negative");
336         }
337         if (mMinDistanceMmSet && mMaxDistanceMmSet && mMaxDistanceMm <= mMinDistanceMm) {
338             throw new IllegalArgumentException(
339                     "Maximum distance must be greater than minimum distance");
340         }
341 
342         if (!rttSupported && (mMinDistanceMmSet || mMaxDistanceMmSet)) {
343             throw new IllegalArgumentException("Ranging is not supported");
344         }
345     }
346 
347     /**
348      * Check if instant mode is enabled for this subscribe session.
349      * @see Builder#setInstantCommunicationModeEnabled(boolean, int)
350      * @return true for enabled, false otherwise.
351      */
isInstantCommunicationModeEnabled()352     public boolean isInstantCommunicationModeEnabled() {
353         return mEnableInstantMode;
354     }
355 
356     /**
357      * Check if enable instant mode on 5G for this subscribe session
358      *
359      * @see Builder#setInstantCommunicationModeEnabled(boolean, int)
360      * @return If instant communication mode is not enabled will return {@link
361      *     ScanResult#WIFI_BAND_24_GHZ} as default.
362      */
363     @WifiAwareManager.InstantModeBand
getInstantCommunicationBand()364     public int getInstantCommunicationBand() {
365         return mBand;
366     }
367 
368     /**
369      * Get the Aware Pairing config for this subscribe session
370      * @see Builder#setPairingConfig(AwarePairingConfig)
371      * @return A {@link AwarePairingConfig} specified in this config.
372      */
373     @Nullable
getPairingConfig()374     public AwarePairingConfig getPairingConfig() {
375         return mPairingConfig;
376     }
377 
378     /**
379      * Check if suspension is supported for this subscribe session.
380      * @see Builder#setSuspendable(boolean)
381      * @return true for supported, false otherwise.
382      * @hide
383      */
384     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
385     @SystemApi
isSuspendable()386     public boolean isSuspendable() {
387         if (!SdkLevel.isAtLeastU()) {
388             throw new UnsupportedOperationException();
389         }
390         return mIsSuspendable;
391     }
392 
393     /**
394      * Return the vendor-provided configuration data, if it exists. See also {@link
395      * Builder#setVendorData(List)}
396      *
397      * @return Vendor configuration data, or empty list if it does not exist.
398      * @hide
399      */
400     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
401     @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
402     @NonNull
403     @SystemApi
getVendorData()404     public List<OuiKeyedData> getVendorData() {
405         if (!SdkLevel.isAtLeastV()) {
406             throw new UnsupportedOperationException();
407         }
408         return mVendorData != null ? mVendorData : Collections.emptyList();
409     }
410 
411     /**
412      * Builder used to build {@link SubscribeConfig} objects.
413      */
414     public static final class Builder {
415         private byte[] mServiceName;
416         private byte[] mServiceSpecificInfo;
417         private byte[] mMatchFilter;
418         private int mSubscribeType = SUBSCRIBE_TYPE_PASSIVE;
419         private int mTtlSec = 0;
420         private boolean mEnableTerminateNotification = true;
421         private boolean mMinDistanceMmSet = false;
422         private int mMinDistanceMm;
423         private boolean mMaxDistanceMmSet = false;
424         private int mMaxDistanceMm;
425         private boolean mEnableInstantMode;
426         private int mBand = WifiScanner.WIFI_BAND_24_GHZ;
427         private AwarePairingConfig mPairingConfig;
428         private boolean mIsSuspendable = false;
429         private @NonNull List<OuiKeyedData> mVendorData = Collections.emptyList();
430 
431         /**
432          * Specify the service name of the subscribe session. The actual on-air
433          * value is a 6 byte hashed representation of this string.
434          * <p>
435          * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length.
436          * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric
437          * values (A-Z, a-z, 0-9), the hyphen ('-'), the period ('.') and the underscore ('_'). All
438          * valid multi-byte UTF-8 characters are acceptable in a Service Name.
439          * <p>
440          * Must be called - an empty ServiceName is not valid.
441          *
442          * @param serviceName The service name for the subscribe session.
443          *
444          * @return The builder to facilitate chaining
445          *         {@code builder.setXXX(..).setXXX(..)}.
446          */
setServiceName(@onNull String serviceName)447         public Builder setServiceName(@NonNull String serviceName) {
448             if (serviceName == null) {
449                 throw new IllegalArgumentException("Invalid service name - must be non-null");
450             }
451             mServiceName = serviceName.getBytes(StandardCharsets.UTF_8);
452             return this;
453         }
454 
455         /**
456          * Specify service specific information for the subscribe session. This is
457          * a free-form byte array available to the application to send
458          * additional information as part of the discovery operation - i.e. it
459          * will not be used to determine whether a publish/subscribe match
460          * occurs.
461          * <p>
462          *     Optional. Empty by default.
463          *
464          * @param serviceSpecificInfo A byte-array for the service-specific
465          *            information field.
466          *
467          * @return The builder to facilitate chaining
468          *         {@code builder.setXXX(..).setXXX(..)}.
469          */
setServiceSpecificInfo(@ullable byte[] serviceSpecificInfo)470         public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) {
471             mServiceSpecificInfo = serviceSpecificInfo;
472             return this;
473         }
474 
475         /**
476          * The match filter for a subscribe session. Used to determine whether a service
477          * discovery occurred - in addition to relying on the service name.
478          * <p>
479          *     Optional. Empty by default.
480          *
481          * @param matchFilter A list of match filter entries (each of which is an arbitrary byte
482          *                    array).
483          *
484          * @return The builder to facilitate chaining
485          *         {@code builder.setXXX(..).setXXX(..)}.
486          */
setMatchFilter(@ullable List<byte[]> matchFilter)487         public Builder setMatchFilter(@Nullable List<byte[]> matchFilter) {
488             mMatchFilter = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut(
489                     matchFilter).getArray();
490             return this;
491         }
492 
493         /**
494          * Sets the type of the subscribe session: active (subscribe packets are
495          * transmitted over-the-air), or passive (no subscribe packets are
496          * transmitted, a match is made against a solicited/active publish
497          * session whose packets are transmitted over-the-air).
498          *
499          * @param subscribeType Subscribe session type:
500          *            {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} or
501          *            {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE}.
502          *
503          * @return The builder to facilitate chaining
504          *         {@code builder.setXXX(..).setXXX(..)}.
505          */
setSubscribeType(@ubscribeTypes int subscribeType)506         public Builder setSubscribeType(@SubscribeTypes int subscribeType) {
507             if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) {
508                 throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType);
509             }
510             mSubscribeType = subscribeType;
511             return this;
512         }
513 
514         /**
515          * Sets the time interval (in seconds) an active (
516          * {@link SubscribeConfig.Builder#setSubscribeType(int)}) subscribe session
517          * will be alive - i.e. broadcasting a packet. When the TTL is reached
518          * an event will be generated for
519          * {@link DiscoverySessionCallback#onSessionTerminated()}.
520          * <p>
521          *     Optional. 0 by default - indicating the session doesn't terminate on its own.
522          *     Session will be terminated when {@link DiscoverySession#close()} is
523          *     called.
524          *
525          * @param ttlSec Lifetime of a subscribe session in seconds.
526          *
527          * @return The builder to facilitate chaining
528          *         {@code builder.setXXX(..).setXXX(..)}.
529          */
setTtlSec(int ttlSec)530         public Builder setTtlSec(int ttlSec) {
531             if (ttlSec < 0) {
532                 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
533             }
534             mTtlSec = ttlSec;
535             return this;
536         }
537 
538         /**
539          * Configure whether a subscribe terminate notification
540          * {@link DiscoverySessionCallback#onSessionTerminated()} is reported
541          * back to the callback.
542          *
543          * @param enable If true the terminate callback will be called when the
544          *            subscribe is terminated. Otherwise it will not be called.
545          *
546          * @return The builder to facilitate chaining
547          *         {@code builder.setXXX(..).setXXX(..)}.
548          */
setTerminateNotificationEnabled(boolean enable)549         public Builder setTerminateNotificationEnabled(boolean enable) {
550             mEnableTerminateNotification = enable;
551             return this;
552         }
553 
554         /**
555          * Configure the minimum distance to a discovered publisher at which to trigger a discovery
556          * notification. I.e. discovery will be triggered if we've found a matching publisher
557          * (based on the other criteria in this configuration) <b>and</b> the distance to the
558          * publisher is larger than the value specified in this API. Can be used in conjunction with
559          * {@link #setMaxDistanceMm(int)} to specify a geofence, i.e. discovery with min <=
560          * distance <= max.
561          * <p>
562          * For ranging to be used in discovery it must also be enabled on the publisher using
563          * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may
564          * not be available or enabled on the publisher or may be temporarily disabled on either
565          * subscriber or publisher - in such cases discovery will proceed without ranging.
566          * <p>
567          * When ranging is enabled and available on both publisher and subscriber and a service
568          * is discovered based on geofence constraints the
569          * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)}
570          * is called, otherwise the
571          * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}
572          * is called.
573          * <p>
574          * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked
575          * as described in {@link android.net.wifi.rtt}.
576          *
577          * @param minDistanceMm Minimum distance, in mm, to the publisher above which to trigger
578          *                      discovery.
579          *
580          * @return The builder to facilitate chaining
581          *         {@code builder.setXXX(..).setXXX(..)}.
582          */
setMinDistanceMm(int minDistanceMm)583         public Builder setMinDistanceMm(int minDistanceMm) {
584             mMinDistanceMm = minDistanceMm;
585             mMinDistanceMmSet = true;
586             return this;
587         }
588 
589         /**
590          * Configure the maximum distance to a discovered publisher at which to trigger a discovery
591          * notification. I.e. discovery will be triggered if we've found a matching publisher
592          * (based on the other criteria in this configuration) <b>and</b> the distance to the
593          * publisher is smaller than the value specified in this API. Can be used in conjunction
594          * with {@link #setMinDistanceMm(int)} to specify a geofence, i.e. discovery with min <=
595          * distance <= max.
596          * <p>
597          * For ranging to be used in discovery it must also be enabled on the publisher using
598          * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may
599          * not be available or enabled on the publisher or may be temporarily disabled on either
600          * subscriber or publisher - in such cases discovery will proceed without ranging.
601          * <p>
602          * When ranging is enabled and available on both publisher and subscriber and a service
603          * is discovered based on geofence constraints the
604          * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)}
605          * is called, otherwise the
606          * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}
607          * is called.
608          * <p>
609          * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked
610          * as described in {@link android.net.wifi.rtt}.
611          *
612          * @param maxDistanceMm Maximum distance, in mm, to the publisher below which to trigger
613          *                      discovery.
614          *
615          * @return The builder to facilitate chaining
616          *         {@code builder.setXXX(..).setXXX(..)}.
617          */
setMaxDistanceMm(int maxDistanceMm)618         public Builder setMaxDistanceMm(int maxDistanceMm) {
619             mMaxDistanceMm = maxDistanceMm;
620             mMaxDistanceMmSet = true;
621             return this;
622         }
623 
624         /**
625          * Configure whether to enable and use instant communication for this subscribe session.
626          * Instant communication will speed up service discovery and any data-path set up as part of
627          * this session. Use {@link Characteristics#isInstantCommunicationModeSupported()} to check
628          * if the device supports this feature.
629          *
630          * <p>Note: due to increased power requirements of this mode - it will only remain enabled
631          * for 30 seconds from the time the discovery session is started.
632          *
633          * @param enabled true for enable instant communication mode, default is false.
634          * @param band When setting to {@link ScanResult#WIFI_BAND_5_GHZ}, device will try to enable
635          *     instant communication mode on 5Ghz, but may fall back to 2.4Ghz due to regulatory
636          *     requirements.
637          * @return the current {@link Builder} builder, enabling chaining of builder methods.
638          */
639         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
640         @NonNull
setInstantCommunicationModeEnabled( boolean enabled, @WifiAwareManager.InstantModeBand int band)641         public Builder setInstantCommunicationModeEnabled(
642                 boolean enabled, @WifiAwareManager.InstantModeBand int band) {
643             if (!SdkLevel.isAtLeastT()) {
644                 throw new UnsupportedOperationException();
645             }
646             if (band != ScanResult.WIFI_BAND_24_GHZ && band != ScanResult.WIFI_BAND_5_GHZ) {
647                 throw new IllegalArgumentException();
648             }
649             mBand = band;
650             mEnableInstantMode = enabled;
651             return this;
652         }
653 
654         /**
655          * Set the {@link AwarePairingConfig} for this subscribe session, the peer can use this info
656          * to determine the config of the following bootstrapping, pairing setup/verification
657          * request.
658          * @see AwarePairingConfig
659          * @param config The pairing config set to the peer. Only valid when
660          * {@link Characteristics#isAwarePairingSupported()} is true.
661          * @return the current {@link Builder} builder, enabling chaining of builder methods.
662          */
663         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
setPairingConfig(@ullable AwarePairingConfig config)664         @NonNull public Builder setPairingConfig(@Nullable AwarePairingConfig config) {
665             if (!SdkLevel.isAtLeastU()) {
666                 throw new UnsupportedOperationException();
667             }
668             mPairingConfig = config;
669             return this;
670         }
671 
672         /**
673          * Specify whether to configure the subscribe discovery session to be suspendable. This API
674          * doesn't suspend the session, it allows it to be suspended and resumed in the future using
675          * {@link DiscoverySession#suspend()} and {@link DiscoverySession#resume()} respectively.
676          * <p>
677          * Optional. Not suspendable by default.
678          * <p>
679          * The device must support Wi-Fi Aware suspension for a subscribe session to be
680          * suspendable. Feature support check is determined by
681          * {@link Characteristics#isSuspensionSupported()}.
682          *
683          * @param isSuspendable If true, then this subscribe session can be suspended.
684          *
685          * @return the current {@link Builder} builder, enabling chaining of builder methods.
686          *
687          * @see DiscoverySession#suspend()
688          * @see DiscoverySession#resume()
689          * @hide
690          */
691         @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
692         @RequiresPermission(value = MANAGE_WIFI_NETWORK_SELECTION)
693         @SystemApi
694         @NonNull
setSuspendable(boolean isSuspendable)695         public Builder setSuspendable(boolean isSuspendable) {
696             if (!SdkLevel.isAtLeastU()) {
697                 throw new UnsupportedOperationException();
698             }
699             mIsSuspendable = isSuspendable;
700             return this;
701         }
702 
703         /**
704          * Set additional vendor-provided configuration data.
705          *
706          * @param vendorData List of {@link OuiKeyedData} containing the vendor-provided
707          *     configuration data. Note that multiple elements with the same OUI are allowed.
708          * @return Builder for chaining.
709          * @hide
710          */
711         @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
712         @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
713         @NonNull
714         @SystemApi
setVendorData(@onNull List<OuiKeyedData> vendorData)715         public Builder setVendorData(@NonNull List<OuiKeyedData> vendorData) {
716             if (!SdkLevel.isAtLeastV()) {
717                 throw new UnsupportedOperationException();
718             }
719             if (vendorData == null) {
720                 throw new IllegalArgumentException("setVendorData received a null value");
721             }
722             mVendorData = vendorData;
723             return this;
724         }
725 
726         /**
727          * Build {@link SubscribeConfig} given the current requests made on the
728          * builder.
729          */
build()730         public SubscribeConfig build() {
731             return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter,
732                     mSubscribeType, mTtlSec, mEnableTerminateNotification,
733                     mMinDistanceMmSet, mMinDistanceMm, mMaxDistanceMmSet, mMaxDistanceMm,
734                     mEnableInstantMode, mBand, mPairingConfig, mIsSuspendable, mVendorData);
735         }
736     }
737 }
738