• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.net.wifi.util.HexEncoding;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.nio.charset.StandardCharsets;
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Objects;
32 
33 /**
34  * Defines the configuration of a Aware subscribe session. Built using
35  * {@link SubscribeConfig.Builder}. Subscribe is done using
36  * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
37  * android.os.Handler)} or
38  * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}.
39  */
40 public final class SubscribeConfig implements Parcelable {
41     /** @hide */
42     @IntDef({
43             SUBSCRIBE_TYPE_PASSIVE, SUBSCRIBE_TYPE_ACTIVE })
44     @Retention(RetentionPolicy.SOURCE)
45     public @interface SubscribeTypes {
46     }
47 
48     /**
49      * Defines a passive subscribe session - a subscribe session where
50      * subscribe packets are not transmitted over-the-air and the device listens
51      * and matches to transmitted publish packets. Configuration is done using
52      * {@link SubscribeConfig.Builder#setSubscribeType(int)}.
53      */
54     public static final int SUBSCRIBE_TYPE_PASSIVE = 0;
55 
56     /**
57      * Defines an active subscribe session - a subscribe session where
58      * subscribe packets are transmitted over-the-air. Configuration is done
59      * using {@link SubscribeConfig.Builder#setSubscribeType(int)}.
60      */
61     public static final int SUBSCRIBE_TYPE_ACTIVE = 1;
62 
63     /** @hide */
64     public final byte[] mServiceName;
65 
66     /** @hide */
67     public final byte[] mServiceSpecificInfo;
68 
69     /** @hide */
70     public final byte[] mMatchFilter;
71 
72     /** @hide */
73     public final int mSubscribeType;
74 
75     /** @hide */
76     public final int mTtlSec;
77 
78     /** @hide */
79     public final boolean mEnableTerminateNotification;
80 
81     /** @hide */
82     public final boolean mMinDistanceMmSet;
83 
84     /** @hide */
85     public final int mMinDistanceMm;
86 
87     /** @hide */
88     public final boolean mMaxDistanceMmSet;
89 
90     /** @hide */
91     public final int mMaxDistanceMm;
92 
93     /** @hide */
SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, int subscribeType, int ttlSec, boolean enableTerminateNotification, boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet, int maxDistanceMm)94     public SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
95             int subscribeType, int ttlSec, boolean enableTerminateNotification,
96             boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet,
97             int maxDistanceMm) {
98         mServiceName = serviceName;
99         mServiceSpecificInfo = serviceSpecificInfo;
100         mMatchFilter = matchFilter;
101         mSubscribeType = subscribeType;
102         mTtlSec = ttlSec;
103         mEnableTerminateNotification = enableTerminateNotification;
104         mMinDistanceMm = minDistanceMm;
105         mMinDistanceMmSet = minDistanceMmSet;
106         mMaxDistanceMm = maxDistanceMm;
107         mMaxDistanceMmSet = maxDistanceMmSet;
108     }
109 
110     @Override
toString()111     public String toString() {
112         return "SubscribeConfig [mServiceName='" + (mServiceName == null ? "<null>"
113                 : String.valueOf(HexEncoding.encode(mServiceName))) + ", mServiceName.length=" + (
114                 mServiceName == null ? 0 : mServiceName.length) + ", mServiceSpecificInfo='" + (
115                 (mServiceSpecificInfo == null) ? "<null>" : String.valueOf(
116                         HexEncoding.encode(mServiceSpecificInfo)))
117                 + ", mServiceSpecificInfo.length=" + (mServiceSpecificInfo == null ? 0
118                 : mServiceSpecificInfo.length) + ", mMatchFilter="
119                 + (new TlvBufferUtils.TlvIterable(0, 1, mMatchFilter)).toString()
120                 + ", mMatchFilter.length=" + (mMatchFilter == null ? 0 : mMatchFilter.length)
121                 + ", mSubscribeType=" + mSubscribeType + ", mTtlSec=" + mTtlSec
122                 + ", mEnableTerminateNotification=" + mEnableTerminateNotification
123                 + ", mMinDistanceMm=" + mMinDistanceMm
124                 + ", mMinDistanceMmSet=" + mMinDistanceMmSet
125                 + ", mMaxDistanceMm=" + mMaxDistanceMm
126                 + ", mMaxDistanceMmSet=" + mMaxDistanceMmSet + "]";
127     }
128 
129     @Override
describeContents()130     public int describeContents() {
131         return 0;
132     }
133 
134     @Override
writeToParcel(Parcel dest, int flags)135     public void writeToParcel(Parcel dest, int flags) {
136         dest.writeByteArray(mServiceName);
137         dest.writeByteArray(mServiceSpecificInfo);
138         dest.writeByteArray(mMatchFilter);
139         dest.writeInt(mSubscribeType);
140         dest.writeInt(mTtlSec);
141         dest.writeInt(mEnableTerminateNotification ? 1 : 0);
142         dest.writeInt(mMinDistanceMm);
143         dest.writeInt(mMinDistanceMmSet ? 1 : 0);
144         dest.writeInt(mMaxDistanceMm);
145         dest.writeInt(mMaxDistanceMmSet ? 1 : 0);
146     }
147 
148     public static final @android.annotation.NonNull Creator<SubscribeConfig> CREATOR = new Creator<SubscribeConfig>() {
149         @Override
150         public SubscribeConfig[] newArray(int size) {
151             return new SubscribeConfig[size];
152         }
153 
154         @Override
155         public SubscribeConfig createFromParcel(Parcel in) {
156             byte[] serviceName = in.createByteArray();
157             byte[] ssi = in.createByteArray();
158             byte[] matchFilter = in.createByteArray();
159             int subscribeType = in.readInt();
160             int ttlSec = in.readInt();
161             boolean enableTerminateNotification = in.readInt() != 0;
162             int minDistanceMm = in.readInt();
163             boolean minDistanceMmSet = in.readInt() != 0;
164             int maxDistanceMm = in.readInt();
165             boolean maxDistanceMmSet = in.readInt() != 0;
166 
167             return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, ttlSec,
168                     enableTerminateNotification, minDistanceMmSet, minDistanceMm, maxDistanceMmSet,
169                     maxDistanceMm);
170         }
171     };
172 
173     @Override
equals(Object o)174     public boolean equals(Object o) {
175         if (this == o) {
176             return true;
177         }
178 
179         if (!(o instanceof SubscribeConfig)) {
180             return false;
181         }
182 
183         SubscribeConfig lhs = (SubscribeConfig) o;
184 
185         if (!(Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(
186                 mServiceSpecificInfo, lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter,
187                 lhs.mMatchFilter) && mSubscribeType == lhs.mSubscribeType && mTtlSec == lhs.mTtlSec
188                 && mEnableTerminateNotification == lhs.mEnableTerminateNotification
189                 && mMinDistanceMmSet == lhs.mMinDistanceMmSet
190                 && mMaxDistanceMmSet == lhs.mMaxDistanceMmSet)) {
191             return false;
192         }
193 
194         if (mMinDistanceMmSet && mMinDistanceMm != lhs.mMinDistanceMm) {
195             return false;
196         }
197 
198         if (mMaxDistanceMmSet && mMaxDistanceMm != lhs.mMaxDistanceMm) {
199             return false;
200         }
201 
202         return true;
203     }
204 
205     @Override
hashCode()206     public int hashCode() {
207         int result = Objects.hash(Arrays.hashCode(mServiceName),
208                 Arrays.hashCode(mServiceSpecificInfo), Arrays.hashCode(mMatchFilter),
209                 mSubscribeType, mTtlSec, mEnableTerminateNotification, mMinDistanceMmSet,
210                 mMaxDistanceMmSet);
211 
212         if (mMinDistanceMmSet) {
213             result = Objects.hash(result, mMinDistanceMm);
214         }
215         if (mMaxDistanceMmSet) {
216             result = Objects.hash(result, mMaxDistanceMm);
217         }
218 
219         return result;
220     }
221 
222     /**
223      * Verifies that the contents of the SubscribeConfig are valid. Otherwise
224      * throws an IllegalArgumentException.
225      *
226      * @hide
227      */
assertValid(Characteristics characteristics, boolean rttSupported)228     public void assertValid(Characteristics characteristics, boolean rttSupported)
229             throws IllegalArgumentException {
230         WifiAwareUtils.validateServiceName(mServiceName);
231 
232         if (!TlvBufferUtils.isValid(mMatchFilter, 0, 1)) {
233             throw new IllegalArgumentException(
234                     "Invalid matchFilter configuration - LV fields do not match up to length");
235         }
236         if (mSubscribeType < SUBSCRIBE_TYPE_PASSIVE || mSubscribeType > SUBSCRIBE_TYPE_ACTIVE) {
237             throw new IllegalArgumentException("Invalid subscribeType - " + mSubscribeType);
238         }
239         if (mTtlSec < 0) {
240             throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
241         }
242 
243         if (characteristics != null) {
244             int maxServiceNameLength = characteristics.getMaxServiceNameLength();
245             if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) {
246                 throw new IllegalArgumentException(
247                         "Service name longer than supported by device characteristics");
248             }
249             int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength();
250             if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null
251                     && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) {
252                 throw new IllegalArgumentException(
253                         "Service specific info longer than supported by device characteristics");
254             }
255             int maxMatchFilterLength = characteristics.getMaxMatchFilterLength();
256             if (maxMatchFilterLength != 0 && mMatchFilter != null
257                     && mMatchFilter.length > maxMatchFilterLength) {
258                 throw new IllegalArgumentException(
259                         "Match filter longer than supported by device characteristics");
260             }
261         }
262 
263         if (mMinDistanceMmSet && mMinDistanceMm < 0) {
264             throw new IllegalArgumentException("Minimum distance must be non-negative");
265         }
266         if (mMaxDistanceMmSet && mMaxDistanceMm < 0) {
267             throw new IllegalArgumentException("Maximum distance must be non-negative");
268         }
269         if (mMinDistanceMmSet && mMaxDistanceMmSet && mMaxDistanceMm <= mMinDistanceMm) {
270             throw new IllegalArgumentException(
271                     "Maximum distance must be greater than minimum distance");
272         }
273 
274         if (!rttSupported && (mMinDistanceMmSet || mMaxDistanceMmSet)) {
275             throw new IllegalArgumentException("Ranging is not supported");
276         }
277     }
278 
279     /**
280      * Builder used to build {@link SubscribeConfig} objects.
281      */
282     public static final class Builder {
283         private byte[] mServiceName;
284         private byte[] mServiceSpecificInfo;
285         private byte[] mMatchFilter;
286         private int mSubscribeType = SUBSCRIBE_TYPE_PASSIVE;
287         private int mTtlSec = 0;
288         private boolean mEnableTerminateNotification = true;
289         private boolean mMinDistanceMmSet = false;
290         private int mMinDistanceMm;
291         private boolean mMaxDistanceMmSet = false;
292         private int mMaxDistanceMm;
293 
294         /**
295          * Specify the service name of the subscribe session. The actual on-air
296          * value is a 6 byte hashed representation of this string.
297          * <p>
298          * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length.
299          * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric
300          * values (A-Z, a-z, 0-9), the hyphen ('-'), the period ('.') and the underscore ('_'). All
301          * valid multi-byte UTF-8 characters are acceptable in a Service Name.
302          * <p>
303          * Must be called - an empty ServiceName is not valid.
304          *
305          * @param serviceName The service name for the subscribe session.
306          *
307          * @return The builder to facilitate chaining
308          *         {@code builder.setXXX(..).setXXX(..)}.
309          */
setServiceName(@onNull String serviceName)310         public Builder setServiceName(@NonNull String serviceName) {
311             if (serviceName == null) {
312                 throw new IllegalArgumentException("Invalid service name - must be non-null");
313             }
314             mServiceName = serviceName.getBytes(StandardCharsets.UTF_8);
315             return this;
316         }
317 
318         /**
319          * Specify service specific information for the subscribe session. This is
320          * a free-form byte array available to the application to send
321          * additional information as part of the discovery operation - i.e. it
322          * will not be used to determine whether a publish/subscribe match
323          * occurs.
324          * <p>
325          *     Optional. Empty by default.
326          *
327          * @param serviceSpecificInfo A byte-array for the service-specific
328          *            information field.
329          *
330          * @return The builder to facilitate chaining
331          *         {@code builder.setXXX(..).setXXX(..)}.
332          */
setServiceSpecificInfo(@ullable byte[] serviceSpecificInfo)333         public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) {
334             mServiceSpecificInfo = serviceSpecificInfo;
335             return this;
336         }
337 
338         /**
339          * The match filter for a subscribe session. Used to determine whether a service
340          * discovery occurred - in addition to relying on the service name.
341          * <p>
342          *     Optional. Empty by default.
343          *
344          * @param matchFilter A list of match filter entries (each of which is an arbitrary byte
345          *                    array).
346          *
347          * @return The builder to facilitate chaining
348          *         {@code builder.setXXX(..).setXXX(..)}.
349          */
setMatchFilter(@ullable List<byte[]> matchFilter)350         public Builder setMatchFilter(@Nullable List<byte[]> matchFilter) {
351             mMatchFilter = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut(
352                     matchFilter).getArray();
353             return this;
354         }
355 
356         /**
357          * Sets the type of the subscribe session: active (subscribe packets are
358          * transmitted over-the-air), or passive (no subscribe packets are
359          * transmitted, a match is made against a solicited/active publish
360          * session whose packets are transmitted over-the-air).
361          *
362          * @param subscribeType Subscribe session type:
363          *            {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} or
364          *            {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE}.
365          *
366          * @return The builder to facilitate chaining
367          *         {@code builder.setXXX(..).setXXX(..)}.
368          */
setSubscribeType(@ubscribeTypes int subscribeType)369         public Builder setSubscribeType(@SubscribeTypes int subscribeType) {
370             if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) {
371                 throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType);
372             }
373             mSubscribeType = subscribeType;
374             return this;
375         }
376 
377         /**
378          * Sets the time interval (in seconds) an active (
379          * {@link SubscribeConfig.Builder#setSubscribeType(int)}) subscribe session
380          * will be alive - i.e. broadcasting a packet. When the TTL is reached
381          * an event will be generated for
382          * {@link DiscoverySessionCallback#onSessionTerminated()}.
383          * <p>
384          *     Optional. 0 by default - indicating the session doesn't terminate on its own.
385          *     Session will be terminated when {@link DiscoverySession#close()} is
386          *     called.
387          *
388          * @param ttlSec Lifetime of a subscribe session in seconds.
389          *
390          * @return The builder to facilitate chaining
391          *         {@code builder.setXXX(..).setXXX(..)}.
392          */
setTtlSec(int ttlSec)393         public Builder setTtlSec(int ttlSec) {
394             if (ttlSec < 0) {
395                 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
396             }
397             mTtlSec = ttlSec;
398             return this;
399         }
400 
401         /**
402          * Configure whether a subscribe terminate notification
403          * {@link DiscoverySessionCallback#onSessionTerminated()} is reported
404          * back to the callback.
405          *
406          * @param enable If true the terminate callback will be called when the
407          *            subscribe is terminated. Otherwise it will not be called.
408          *
409          * @return The builder to facilitate chaining
410          *         {@code builder.setXXX(..).setXXX(..)}.
411          */
setTerminateNotificationEnabled(boolean enable)412         public Builder setTerminateNotificationEnabled(boolean enable) {
413             mEnableTerminateNotification = enable;
414             return this;
415         }
416 
417         /**
418          * Configure the minimum distance to a discovered publisher at which to trigger a discovery
419          * notification. I.e. discovery will be triggered if we've found a matching publisher
420          * (based on the other criteria in this configuration) <b>and</b> the distance to the
421          * publisher is larger than the value specified in this API. Can be used in conjunction with
422          * {@link #setMaxDistanceMm(int)} to specify a geofence, i.e. discovery with min <=
423          * distance <= max.
424          * <p>
425          * For ranging to be used in discovery it must also be enabled on the publisher using
426          * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may
427          * not be available or enabled on the publisher or may be temporarily disabled on either
428          * subscriber or publisher - in such cases discovery will proceed without ranging.
429          * <p>
430          * When ranging is enabled and available on both publisher and subscriber and a service
431          * is discovered based on geofence constraints the
432          * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)}
433          * is called, otherwise the
434          * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}
435          * is called.
436          * <p>
437          * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked
438          * as described in {@link android.net.wifi.rtt}.
439          *
440          * @param minDistanceMm Minimum distance, in mm, to the publisher above which to trigger
441          *                      discovery.
442          *
443          * @return The builder to facilitate chaining
444          *         {@code builder.setXXX(..).setXXX(..)}.
445          */
setMinDistanceMm(int minDistanceMm)446         public Builder setMinDistanceMm(int minDistanceMm) {
447             mMinDistanceMm = minDistanceMm;
448             mMinDistanceMmSet = true;
449             return this;
450         }
451 
452         /**
453          * Configure the maximum distance to a discovered publisher at which to trigger a discovery
454          * notification. I.e. discovery will be triggered if we've found a matching publisher
455          * (based on the other criteria in this configuration) <b>and</b> the distance to the
456          * publisher is smaller than the value specified in this API. Can be used in conjunction
457          * with {@link #setMinDistanceMm(int)} to specify a geofence, i.e. discovery with min <=
458          * distance <= max.
459          * <p>
460          * For ranging to be used in discovery it must also be enabled on the publisher using
461          * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may
462          * not be available or enabled on the publisher or may be temporarily disabled on either
463          * subscriber or publisher - in such cases discovery will proceed without ranging.
464          * <p>
465          * When ranging is enabled and available on both publisher and subscriber and a service
466          * is discovered based on geofence constraints the
467          * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)}
468          * is called, otherwise the
469          * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}
470          * is called.
471          * <p>
472          * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked
473          * as described in {@link android.net.wifi.rtt}.
474          *
475          * @param maxDistanceMm Maximum distance, in mm, to the publisher below which to trigger
476          *                      discovery.
477          *
478          * @return The builder to facilitate chaining
479          *         {@code builder.setXXX(..).setXXX(..)}.
480          */
setMaxDistanceMm(int maxDistanceMm)481         public Builder setMaxDistanceMm(int maxDistanceMm) {
482             mMaxDistanceMm = maxDistanceMm;
483             mMaxDistanceMmSet = true;
484             return this;
485         }
486 
487         /**
488          * Build {@link SubscribeConfig} given the current requests made on the
489          * builder.
490          */
build()491         public SubscribeConfig build() {
492             return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter,
493                     mSubscribeType, mTtlSec, mEnableTerminateNotification,
494                     mMinDistanceMmSet, mMinDistanceMm, mMaxDistanceMmSet, mMaxDistanceMm);
495         }
496     }
497 }
498