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