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