• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.companion;
18 
19 import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
20 import static android.companion.BluetoothDeviceFilterUtils.patternFromString;
21 import static android.companion.BluetoothDeviceFilterUtils.patternToString;
22 
23 import static com.android.internal.util.Preconditions.checkArgument;
24 import static com.android.internal.util.Preconditions.checkState;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.annotation.UnsupportedAppUsage;
29 import android.bluetooth.BluetoothDevice;
30 import android.bluetooth.le.ScanFilter;
31 import android.bluetooth.le.ScanRecord;
32 import android.bluetooth.le.ScanResult;
33 import android.os.Parcel;
34 import android.provider.OneTimeUseBuilder;
35 import android.text.TextUtils;
36 import android.util.Log;
37 
38 import com.android.internal.util.BitUtils;
39 import com.android.internal.util.ObjectUtils;
40 import com.android.internal.util.Preconditions;
41 
42 import java.nio.ByteOrder;
43 import java.util.Arrays;
44 import java.util.Objects;
45 import java.util.regex.Pattern;
46 
47 /**
48  * A filter for Bluetooth LE devices
49  *
50  * @see ScanFilter
51  */
52 public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> {
53 
54     private static final boolean DEBUG = false;
55     private static final String LOG_TAG = "BluetoothLeDeviceFilter";
56 
57     private static final int RENAME_PREFIX_LENGTH_LIMIT = 10;
58 
59     private final Pattern mNamePattern;
60     private final ScanFilter mScanFilter;
61     private final byte[] mRawDataFilter;
62     private final byte[] mRawDataFilterMask;
63     private final String mRenamePrefix;
64     private final String mRenameSuffix;
65     private final int mRenameBytesFrom;
66     private final int mRenameBytesLength;
67     private final int mRenameNameFrom;
68     private final int mRenameNameLength;
69     private final boolean mRenameBytesReverseOrder;
70 
BluetoothLeDeviceFilter(Pattern namePattern, ScanFilter scanFilter, byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix, String renameSuffix, int renameBytesFrom, int renameBytesLength, int renameNameFrom, int renameNameLength, boolean renameBytesReverseOrder)71     private BluetoothLeDeviceFilter(Pattern namePattern, ScanFilter scanFilter,
72             byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix,
73             String renameSuffix, int renameBytesFrom, int renameBytesLength,
74             int renameNameFrom, int renameNameLength, boolean renameBytesReverseOrder) {
75         mNamePattern = namePattern;
76         mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY);
77         mRawDataFilter = rawDataFilter;
78         mRawDataFilterMask = rawDataFilterMask;
79         mRenamePrefix = renamePrefix;
80         mRenameSuffix = renameSuffix;
81         mRenameBytesFrom = renameBytesFrom;
82         mRenameBytesLength = renameBytesLength;
83         mRenameNameFrom = renameNameFrom;
84         mRenameNameLength = renameNameLength;
85         mRenameBytesReverseOrder = renameBytesReverseOrder;
86     }
87 
88     /** @hide */
89     @Nullable
getNamePattern()90     public Pattern getNamePattern() {
91         return mNamePattern;
92     }
93 
94     /** @hide */
95     @NonNull
96     @UnsupportedAppUsage
getScanFilter()97     public ScanFilter getScanFilter() {
98         return mScanFilter;
99     }
100 
101     /** @hide */
102     @Nullable
getRawDataFilter()103     public byte[] getRawDataFilter() {
104         return mRawDataFilter;
105     }
106 
107     /** @hide */
108     @Nullable
getRawDataFilterMask()109     public byte[] getRawDataFilterMask() {
110         return mRawDataFilterMask;
111     }
112 
113     /** @hide */
114     @Nullable
getRenamePrefix()115     public String getRenamePrefix() {
116         return mRenamePrefix;
117     }
118 
119     /** @hide */
120     @Nullable
getRenameSuffix()121     public String getRenameSuffix() {
122         return mRenameSuffix;
123     }
124 
125     /** @hide */
getRenameBytesFrom()126     public int getRenameBytesFrom() {
127         return mRenameBytesFrom;
128     }
129 
130     /** @hide */
getRenameBytesLength()131     public int getRenameBytesLength() {
132         return mRenameBytesLength;
133     }
134 
135     /** @hide */
isRenameBytesReverseOrder()136     public boolean isRenameBytesReverseOrder() {
137         return mRenameBytesReverseOrder;
138     }
139 
140     /** @hide */
141     @Override
142     @Nullable
getDeviceDisplayName(ScanResult sr)143     public String getDeviceDisplayName(ScanResult sr) {
144         if (mRenameBytesFrom < 0 && mRenameNameFrom < 0) {
145             return getDeviceDisplayNameInternal(sr.getDevice());
146         }
147         final StringBuilder sb = new StringBuilder(TextUtils.emptyIfNull(mRenamePrefix));
148         if (mRenameBytesFrom >= 0) {
149             final byte[] bytes = sr.getScanRecord().getBytes();
150             int startInclusive = mRenameBytesFrom;
151             int endInclusive = mRenameBytesFrom + mRenameBytesLength -1;
152             int initial = mRenameBytesReverseOrder ? endInclusive : startInclusive;
153             int step = mRenameBytesReverseOrder ? -1 : 1;
154             for (int i = initial; startInclusive <= i && i <= endInclusive; i += step) {
155                 sb.append(Byte.toHexString(bytes[i], true));
156             }
157         } else {
158             sb.append(
159                     getDeviceDisplayNameInternal(sr.getDevice())
160                             .substring(mRenameNameFrom, mRenameNameFrom + mRenameNameLength));
161         }
162         return sb.append(TextUtils.emptyIfNull(mRenameSuffix)).toString();
163     }
164 
165     /** @hide */
166     @Override
matches(ScanResult device)167     public boolean matches(ScanResult device) {
168         boolean result = matches(device.getDevice())
169                 && (mRawDataFilter == null
170                     || BitUtils.maskedEquals(device.getScanRecord().getBytes(),
171                             mRawDataFilter, mRawDataFilterMask));
172         if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device +
173                 ") -> " + result);
174         return result;
175     }
176 
matches(BluetoothDevice device)177     private boolean matches(BluetoothDevice device) {
178         return BluetoothDeviceFilterUtils.matches(getScanFilter(), device)
179                 && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device);
180     }
181 
182     /** @hide */
183     @Override
getMediumType()184     public int getMediumType() {
185         return DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE;
186     }
187 
188     @Override
equals(Object o)189     public boolean equals(Object o) {
190         if (this == o) return true;
191         if (o == null || getClass() != o.getClass()) return false;
192         BluetoothLeDeviceFilter that = (BluetoothLeDeviceFilter) o;
193         return mRenameBytesFrom == that.mRenameBytesFrom &&
194                 mRenameBytesLength == that.mRenameBytesLength &&
195                 mRenameNameFrom == that.mRenameNameFrom &&
196                 mRenameNameLength == that.mRenameNameLength &&
197                 mRenameBytesReverseOrder == that.mRenameBytesReverseOrder &&
198                 Objects.equals(mNamePattern, that.mNamePattern) &&
199                 Objects.equals(mScanFilter, that.mScanFilter) &&
200                 Arrays.equals(mRawDataFilter, that.mRawDataFilter) &&
201                 Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) &&
202                 Objects.equals(mRenamePrefix, that.mRenamePrefix) &&
203                 Objects.equals(mRenameSuffix, that.mRenameSuffix);
204     }
205 
206     @Override
hashCode()207     public int hashCode() {
208         return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask,
209                 mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesLength,
210                 mRenameNameFrom, mRenameNameLength, mRenameBytesReverseOrder);
211     }
212 
213     @Override
writeToParcel(Parcel dest, int flags)214     public void writeToParcel(Parcel dest, int flags) {
215         dest.writeString(patternToString(getNamePattern()));
216         dest.writeParcelable(mScanFilter, flags);
217         dest.writeByteArray(mRawDataFilter);
218         dest.writeByteArray(mRawDataFilterMask);
219         dest.writeString(mRenamePrefix);
220         dest.writeString(mRenameSuffix);
221         dest.writeInt(mRenameBytesFrom);
222         dest.writeInt(mRenameBytesLength);
223         dest.writeInt(mRenameNameFrom);
224         dest.writeInt(mRenameNameLength);
225         dest.writeBoolean(mRenameBytesReverseOrder);
226     }
227 
228     @Override
describeContents()229     public int describeContents() {
230         return 0;
231     }
232 
233     @Override
toString()234     public String toString() {
235         return "BluetoothLEDeviceFilter{" +
236                 "mNamePattern=" + mNamePattern +
237                 ", mScanFilter=" + mScanFilter +
238                 ", mRawDataFilter=" + Arrays.toString(mRawDataFilter) +
239                 ", mRawDataFilterMask=" + Arrays.toString(mRawDataFilterMask) +
240                 ", mRenamePrefix='" + mRenamePrefix + '\'' +
241                 ", mRenameSuffix='" + mRenameSuffix + '\'' +
242                 ", mRenameBytesFrom=" + mRenameBytesFrom +
243                 ", mRenameBytesLength=" + mRenameBytesLength +
244                 ", mRenameNameFrom=" + mRenameNameFrom +
245                 ", mRenameNameLength=" + mRenameNameLength +
246                 ", mRenameBytesReverseOrder=" + mRenameBytesReverseOrder +
247                 '}';
248     }
249 
250     public static final @android.annotation.NonNull Creator<BluetoothLeDeviceFilter> CREATOR
251             = new Creator<BluetoothLeDeviceFilter>() {
252         @Override
253         public BluetoothLeDeviceFilter createFromParcel(Parcel in) {
254             Builder builder = new Builder()
255                     .setNamePattern(patternFromString(in.readString()))
256                     .setScanFilter(in.readParcelable(null));
257             byte[] rawDataFilter = in.createByteArray();
258             byte[] rawDataFilterMask = in.createByteArray();
259             if (rawDataFilter != null) {
260                 builder.setRawDataFilter(rawDataFilter, rawDataFilterMask);
261             }
262             String renamePrefix = in.readString();
263             String suffix = in.readString();
264             int bytesFrom = in.readInt();
265             int bytesTo = in.readInt();
266             int nameFrom = in.readInt();
267             int nameTo = in.readInt();
268             boolean bytesReverseOrder = in.readBoolean();
269             if (renamePrefix != null) {
270                 if (bytesFrom >= 0) {
271                     builder.setRenameFromBytes(renamePrefix, suffix, bytesFrom, bytesTo,
272                             bytesReverseOrder ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
273                 } else {
274                     builder.setRenameFromName(renamePrefix, suffix, nameFrom, nameTo);
275                 }
276             }
277             return builder.build();
278         }
279 
280         @Override
281         public BluetoothLeDeviceFilter[] newArray(int size) {
282             return new BluetoothLeDeviceFilter[size];
283         }
284     };
285 
getRenamePrefixLengthLimit()286     public static int getRenamePrefixLengthLimit() {
287         return RENAME_PREFIX_LENGTH_LIMIT;
288     }
289 
290     /**
291      * Builder for {@link BluetoothLeDeviceFilter}
292      */
293     public static final class Builder extends OneTimeUseBuilder<BluetoothLeDeviceFilter> {
294         private ScanFilter mScanFilter;
295         private Pattern mNamePattern;
296         private byte[] mRawDataFilter;
297         private byte[] mRawDataFilterMask;
298         private String mRenamePrefix;
299         private String mRenameSuffix;
300         private int mRenameBytesFrom = -1;
301         private int mRenameBytesLength;
302         private int mRenameNameFrom = -1;
303         private int mRenameNameLength;
304         private boolean mRenameBytesReverseOrder = false;
305 
306         /**
307          * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the
308          *              given regular expression will be shown
309          * @return self for chaining
310          */
setNamePattern(@ullable Pattern regex)311         public Builder setNamePattern(@Nullable Pattern regex) {
312             checkNotUsed();
313             mNamePattern = regex;
314             return this;
315         }
316 
317         /**
318          * @param scanFilter a {@link ScanFilter} to filter devices by
319          *
320          * @return self for chaining
321          * @see ScanFilter for specific details on its various fields
322          */
323         @NonNull
setScanFilter(@ullable ScanFilter scanFilter)324         public Builder setScanFilter(@Nullable ScanFilter scanFilter) {
325             checkNotUsed();
326             mScanFilter = scanFilter;
327             return this;
328         }
329 
330         /**
331          * Filter devices by raw advertisement data, as obtained by {@link ScanRecord#getBytes}
332          *
333          * @param rawDataFilter bit values that have to match against advertized data
334          * @param rawDataFilterMask bits that have to be matched
335          * @return self for chaining
336          */
337         @NonNull
setRawDataFilter(@onNull byte[] rawDataFilter, @Nullable byte[] rawDataFilterMask)338         public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
339                 @Nullable byte[] rawDataFilterMask) {
340             checkNotUsed();
341             Preconditions.checkNotNull(rawDataFilter);
342             checkArgument(rawDataFilterMask == null ||
343                     rawDataFilter.length == rawDataFilterMask.length,
344                     "Mask and filter should be the same length");
345             mRawDataFilter = rawDataFilter;
346             mRawDataFilterMask = rawDataFilterMask;
347             return this;
348         }
349 
350         /**
351          * Rename the devices shown in the list, using specific bytes from the raw advertisement
352          * data ({@link ScanRecord#getBytes}) in hexadecimal format, as well as a custom
353          * prefix/suffix around them
354          *
355          * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters
356          * to ensure that there's enough space to display the byte data
357          *
358          * The range of bytes to be displayed cannot be empty
359          *
360          * @param prefix to be displayed before the byte data
361          * @param suffix to be displayed after the byte data
362          * @param bytesFrom the start byte index to be displayed (inclusive)
363          * @param bytesLength the number of bytes to be displayed from the given index
364          * @param byteOrder whether the given range of bytes is big endian (will be displayed
365          *                   in same order) or little endian (will be flipped before displaying)
366          * @return self for chaining
367          */
368         @NonNull
setRenameFromBytes(@onNull String prefix, @NonNull String suffix, int bytesFrom, int bytesLength, ByteOrder byteOrder)369         public Builder setRenameFromBytes(@NonNull String prefix, @NonNull String suffix,
370                 int bytesFrom, int bytesLength, ByteOrder byteOrder) {
371             checkRenameNotSet();
372             checkRangeNotEmpty(bytesLength);
373             mRenameBytesFrom = bytesFrom;
374             mRenameBytesLength = bytesLength;
375             mRenameBytesReverseOrder = byteOrder == ByteOrder.LITTLE_ENDIAN;
376             return setRename(prefix, suffix);
377         }
378 
379         /**
380          * Rename the devices shown in the list, using specific characters from the advertised name,
381          * as well as a custom prefix/suffix around them
382          *
383          * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters
384          * to ensure that there's enough space to display the byte data
385          *
386          * The range of name characters to be displayed cannot be empty
387          *
388          * @param prefix to be displayed before the byte data
389          * @param suffix to be displayed after the byte data
390          * @param nameFrom the start name character index to be displayed (inclusive)
391          * @param nameLength the number of characters to be displayed from the given index
392          * @return self for chaining
393          */
394         @NonNull
setRenameFromName(@onNull String prefix, @NonNull String suffix, int nameFrom, int nameLength)395         public Builder setRenameFromName(@NonNull String prefix, @NonNull String suffix,
396                 int nameFrom, int nameLength) {
397             checkRenameNotSet();
398             checkRangeNotEmpty(nameLength);
399             mRenameNameFrom = nameFrom;
400             mRenameNameLength = nameLength;
401             mRenameBytesReverseOrder = false;
402             return setRename(prefix, suffix);
403         }
404 
checkRenameNotSet()405         private void checkRenameNotSet() {
406             checkState(mRenamePrefix == null, "Renaming rule can only be set once");
407         }
408 
checkRangeNotEmpty(int length)409         private void checkRangeNotEmpty(int length) {
410             checkArgument(length > 0, "Range must be non-empty");
411         }
412 
413         @NonNull
setRename(@onNull String prefix, @NonNull String suffix)414         private Builder setRename(@NonNull String prefix, @NonNull String suffix) {
415             checkNotUsed();
416             checkArgument(TextUtils.length(prefix) <= getRenamePrefixLengthLimit(),
417                     "Prefix is too long");
418             mRenamePrefix = prefix;
419             mRenameSuffix = suffix;
420             return this;
421         }
422 
423         /** @inheritDoc */
424         @Override
425         @NonNull
build()426         public BluetoothLeDeviceFilter build() {
427             markUsed();
428             return new BluetoothLeDeviceFilter(mNamePattern, mScanFilter,
429                     mRawDataFilter, mRawDataFilterMask,
430                     mRenamePrefix, mRenameSuffix,
431                     mRenameBytesFrom, mRenameBytesLength,
432                     mRenameNameFrom, mRenameNameLength,
433                     mRenameBytesReverseOrder);
434         }
435     }
436 }
437