Home | History | Annotate | Download | only in aware
      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