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 */ SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter, int subscribeType, int ttlSec, boolean enableTerminateNotification, boolean minDistanceMmSet, int minDistanceMm, boolean maxDistanceMmSet, int maxDistanceMm)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 toString()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 describeContents()131 public int describeContents() { 132 return 0; 133 } 134 135 @Override writeToParcel(Parcel dest, int flags)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 @android.annotation.NonNull 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 equals(Object o)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 hashCode()207 public int hashCode() { 208 int result = Objects.hash(Arrays.hashCode(mServiceName), 209 Arrays.hashCode(mServiceSpecificInfo), Arrays.hashCode(mMatchFilter), 210 mSubscribeType, mTtlSec, mEnableTerminateNotification, mMinDistanceMmSet, 211 mMaxDistanceMmSet); 212 213 if (mMinDistanceMmSet) { 214 result = Objects.hash(result, mMinDistanceMm); 215 } 216 if (mMaxDistanceMmSet) { 217 result = Objects.hash(result, mMaxDistanceMm); 218 } 219 220 return result; 221 } 222 223 /** 224 * Verifies that the contents of the SubscribeConfig are valid. Otherwise 225 * throws an IllegalArgumentException. 226 * 227 * @hide 228 */ assertValid(Characteristics characteristics, boolean rttSupported)229 public void assertValid(Characteristics characteristics, boolean rttSupported) 230 throws IllegalArgumentException { 231 WifiAwareUtils.validateServiceName(mServiceName); 232 233 if (!TlvBufferUtils.isValid(mMatchFilter, 0, 1)) { 234 throw new IllegalArgumentException( 235 "Invalid matchFilter configuration - LV fields do not match up to length"); 236 } 237 if (mSubscribeType < SUBSCRIBE_TYPE_PASSIVE || mSubscribeType > SUBSCRIBE_TYPE_ACTIVE) { 238 throw new IllegalArgumentException("Invalid subscribeType - " + mSubscribeType); 239 } 240 if (mTtlSec < 0) { 241 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative"); 242 } 243 244 if (characteristics != null) { 245 int maxServiceNameLength = characteristics.getMaxServiceNameLength(); 246 if (maxServiceNameLength != 0 && mServiceName.length > maxServiceNameLength) { 247 throw new IllegalArgumentException( 248 "Service name longer than supported by device characteristics"); 249 } 250 int maxServiceSpecificInfoLength = characteristics.getMaxServiceSpecificInfoLength(); 251 if (maxServiceSpecificInfoLength != 0 && mServiceSpecificInfo != null 252 && mServiceSpecificInfo.length > maxServiceSpecificInfoLength) { 253 throw new IllegalArgumentException( 254 "Service specific info longer than supported by device characteristics"); 255 } 256 int maxMatchFilterLength = characteristics.getMaxMatchFilterLength(); 257 if (maxMatchFilterLength != 0 && mMatchFilter != null 258 && mMatchFilter.length > maxMatchFilterLength) { 259 throw new IllegalArgumentException( 260 "Match filter longer than supported by device characteristics"); 261 } 262 } 263 264 if (mMinDistanceMmSet && mMinDistanceMm < 0) { 265 throw new IllegalArgumentException("Minimum distance must be non-negative"); 266 } 267 if (mMaxDistanceMmSet && mMaxDistanceMm < 0) { 268 throw new IllegalArgumentException("Maximum distance must be non-negative"); 269 } 270 if (mMinDistanceMmSet && mMaxDistanceMmSet && mMaxDistanceMm <= mMinDistanceMm) { 271 throw new IllegalArgumentException( 272 "Maximum distance must be greater than minimum distance"); 273 } 274 275 if (!rttSupported && (mMinDistanceMmSet || mMaxDistanceMmSet)) { 276 throw new IllegalArgumentException("Ranging is not supported"); 277 } 278 } 279 280 /** 281 * Builder used to build {@link SubscribeConfig} objects. 282 */ 283 public static final class Builder { 284 private byte[] mServiceName; 285 private byte[] mServiceSpecificInfo; 286 private byte[] mMatchFilter; 287 private int mSubscribeType = SUBSCRIBE_TYPE_PASSIVE; 288 private int mTtlSec = 0; 289 private boolean mEnableTerminateNotification = true; 290 private boolean mMinDistanceMmSet = false; 291 private int mMinDistanceMm; 292 private boolean mMaxDistanceMmSet = false; 293 private int mMaxDistanceMm; 294 295 /** 296 * Specify the service name of the subscribe session. The actual on-air 297 * value is a 6 byte hashed representation of this string. 298 * <p> 299 * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length. 300 * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric 301 * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte 302 * UTF-8 characters are acceptable in a Service Name. 303 * <p> 304 * Must be called - an empty ServiceName is not valid. 305 * 306 * @param serviceName The service name for the subscribe session. 307 * 308 * @return The builder to facilitate chaining 309 * {@code builder.setXXX(..).setXXX(..)}. 310 */ setServiceName(@onNull String serviceName)311 public Builder setServiceName(@NonNull String serviceName) { 312 if (serviceName == null) { 313 throw new IllegalArgumentException("Invalid service name - must be non-null"); 314 } 315 mServiceName = serviceName.getBytes(StandardCharsets.UTF_8); 316 return this; 317 } 318 319 /** 320 * Specify service specific information for the subscribe session. This is 321 * a free-form byte array available to the application to send 322 * additional information as part of the discovery operation - i.e. it 323 * will not be used to determine whether a publish/subscribe match 324 * occurs. 325 * <p> 326 * Optional. Empty by default. 327 * 328 * @param serviceSpecificInfo A byte-array for the service-specific 329 * information field. 330 * 331 * @return The builder to facilitate chaining 332 * {@code builder.setXXX(..).setXXX(..)}. 333 */ setServiceSpecificInfo(@ullable byte[] serviceSpecificInfo)334 public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) { 335 mServiceSpecificInfo = serviceSpecificInfo; 336 return this; 337 } 338 339 /** 340 * The match filter for a subscribe session. Used to determine whether a service 341 * discovery occurred - in addition to relying on the service name. 342 * <p> 343 * Optional. Empty by default. 344 * 345 * @param matchFilter A list of match filter entries (each of which is an arbitrary byte 346 * array). 347 * 348 * @return The builder to facilitate chaining 349 * {@code builder.setXXX(..).setXXX(..)}. 350 */ setMatchFilter(@ullable List<byte[]> matchFilter)351 public Builder setMatchFilter(@Nullable List<byte[]> matchFilter) { 352 mMatchFilter = new TlvBufferUtils.TlvConstructor(0, 1).allocateAndPut( 353 matchFilter).getArray(); 354 return this; 355 } 356 357 /** 358 * Sets the type of the subscribe session: active (subscribe packets are 359 * transmitted over-the-air), or passive (no subscribe packets are 360 * transmitted, a match is made against a solicited/active publish 361 * session whose packets are transmitted over-the-air). 362 * 363 * @param subscribeType Subscribe session type: 364 * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} or 365 * {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE}. 366 * 367 * @return The builder to facilitate chaining 368 * {@code builder.setXXX(..).setXXX(..)}. 369 */ setSubscribeType(@ubscribeTypes int subscribeType)370 public Builder setSubscribeType(@SubscribeTypes int subscribeType) { 371 if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) { 372 throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType); 373 } 374 mSubscribeType = subscribeType; 375 return this; 376 } 377 378 /** 379 * Sets the time interval (in seconds) an active ( 380 * {@link SubscribeConfig.Builder#setSubscribeType(int)}) subscribe session 381 * will be alive - i.e. broadcasting a packet. When the TTL is reached 382 * an event will be generated for 383 * {@link DiscoverySessionCallback#onSessionTerminated()}. 384 * <p> 385 * Optional. 0 by default - indicating the session doesn't terminate on its own. 386 * Session will be terminated when {@link DiscoverySession#close()} is 387 * called. 388 * 389 * @param ttlSec Lifetime of a subscribe session in seconds. 390 * 391 * @return The builder to facilitate chaining 392 * {@code builder.setXXX(..).setXXX(..)}. 393 */ setTtlSec(int ttlSec)394 public Builder setTtlSec(int ttlSec) { 395 if (ttlSec < 0) { 396 throw new IllegalArgumentException("Invalid ttlSec - must be non-negative"); 397 } 398 mTtlSec = ttlSec; 399 return this; 400 } 401 402 /** 403 * Configure whether a subscribe terminate notification 404 * {@link DiscoverySessionCallback#onSessionTerminated()} is reported 405 * back to the callback. 406 * 407 * @param enable If true the terminate callback will be called when the 408 * subscribe is terminated. Otherwise it will not be called. 409 * 410 * @return The builder to facilitate chaining 411 * {@code builder.setXXX(..).setXXX(..)}. 412 */ setTerminateNotificationEnabled(boolean enable)413 public Builder setTerminateNotificationEnabled(boolean enable) { 414 mEnableTerminateNotification = enable; 415 return this; 416 } 417 418 /** 419 * Configure the minimum distance to a discovered publisher at which to trigger a discovery 420 * notification. I.e. discovery will be triggered if we've found a matching publisher 421 * (based on the other criteria in this configuration) <b>and</b> the distance to the 422 * publisher is larger than the value specified in this API. Can be used in conjunction with 423 * {@link #setMaxDistanceMm(int)} to specify a geofence, i.e. discovery with min <= 424 * distance <= max. 425 * <p> 426 * For ranging to be used in discovery it must also be enabled on the publisher using 427 * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may 428 * not be available or enabled on the publisher or may be temporarily disabled on either 429 * subscriber or publisher - in such cases discovery will proceed without ranging. 430 * <p> 431 * When ranging is enabled and available on both publisher and subscriber and a service 432 * is discovered based on geofence constraints the 433 * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)} 434 * is called, otherwise the 435 * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)} 436 * is called. 437 * <p> 438 * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked 439 * as described in {@link android.net.wifi.rtt}. 440 * 441 * @param minDistanceMm Minimum distance, in mm, to the publisher above which to trigger 442 * discovery. 443 * 444 * @return The builder to facilitate chaining 445 * {@code builder.setXXX(..).setXXX(..)}. 446 */ setMinDistanceMm(int minDistanceMm)447 public Builder setMinDistanceMm(int minDistanceMm) { 448 mMinDistanceMm = minDistanceMm; 449 mMinDistanceMmSet = true; 450 return this; 451 } 452 453 /** 454 * Configure the maximum distance to a discovered publisher at which to trigger a discovery 455 * notification. I.e. discovery will be triggered if we've found a matching publisher 456 * (based on the other criteria in this configuration) <b>and</b> the distance to the 457 * publisher is smaller than the value specified in this API. Can be used in conjunction 458 * with {@link #setMinDistanceMm(int)} to specify a geofence, i.e. discovery with min <= 459 * distance <= max. 460 * <p> 461 * For ranging to be used in discovery it must also be enabled on the publisher using 462 * {@link PublishConfig.Builder#setRangingEnabled(boolean)}. However, ranging may 463 * not be available or enabled on the publisher or may be temporarily disabled on either 464 * subscriber or publisher - in such cases discovery will proceed without ranging. 465 * <p> 466 * When ranging is enabled and available on both publisher and subscriber and a service 467 * is discovered based on geofence constraints the 468 * {@link DiscoverySessionCallback#onServiceDiscoveredWithinRange(PeerHandle, byte[], List, int)} 469 * is called, otherwise the 470 * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)} 471 * is called. 472 * <p> 473 * The device must support Wi-Fi RTT for this feature to be used. Feature support is checked 474 * as described in {@link android.net.wifi.rtt}. 475 * 476 * @param maxDistanceMm Maximum distance, in mm, to the publisher below which to trigger 477 * discovery. 478 * 479 * @return The builder to facilitate chaining 480 * {@code builder.setXXX(..).setXXX(..)}. 481 */ setMaxDistanceMm(int maxDistanceMm)482 public Builder setMaxDistanceMm(int maxDistanceMm) { 483 mMaxDistanceMm = maxDistanceMm; 484 mMaxDistanceMmSet = true; 485 return this; 486 } 487 488 /** 489 * Build {@link SubscribeConfig} given the current requests made on the 490 * builder. 491 */ build()492 public SubscribeConfig build() { 493 return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, 494 mSubscribeType, mTtlSec, mEnableTerminateNotification, 495 mMinDistanceMmSet, mMinDistanceMm, mMaxDistanceMmSet, mMaxDistanceMm); 496 } 497 } 498 } 499