2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.companion;
19 import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
20 import static android.companion.BluetoothDeviceFilterUtils.patternFromString;
21 import static android.companion.BluetoothDeviceFilterUtils.patternToString;
23 import static com.android.internal.util.Preconditions.checkArgument;
24 import static com.android.internal.util.Preconditions.checkState;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.bluetooth.BluetoothDevice;
29 import android.bluetooth.le.ScanFilter;
30 import android.bluetooth.le.ScanRecord;
31 import android.bluetooth.le.ScanResult;
32 import android.os.Parcel;
33 import android.provider.OneTimeUseBuilder;
34 import android.text.TextUtils;
35 import android.util.Log;
37 import com.android.internal.util.BitUtils;
38 import com.android.internal.util.ObjectUtils;
39 import com.android.internal.util.Preconditions;
41 import java.nio.ByteOrder;
42 import java.util.Arrays;
43 import java.util.Objects;
44 import java.util.regex.Pattern;
47 * A filter for Bluetooth LE devices
51 public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> {
53 private static final boolean DEBUG = false;
54 private static final String LOG_TAG = "BluetoothLeDeviceFilter";
56 private static final int RENAME_PREFIX_LENGTH_LIMIT = 10;
58 private final Pattern mNamePattern;
59 private final ScanFilter mScanFilter;
60 private final byte[] mRawDataFilter;
61 private final byte[] mRawDataFilterMask;
62 private final String mRenamePrefix;
63 private final String mRenameSuffix;
64 private final int mRenameBytesFrom;
65 private final int mRenameBytesLength;
66 private final int mRenameNameFrom;
67 private final int mRenameNameLength;
68 private final boolean mRenameBytesReverseOrder;
70 private BluetoothLeDeviceFilter(Pattern namePattern, ScanFilter scanFilter,
71 byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix,
72 String renameSuffix, int renameBytesFrom, int renameBytesLength,
73 int renameNameFrom, int renameNameLength, boolean renameBytesReverseOrder) {
74 mNamePattern = namePattern;
75 mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY);
76 mRawDataFilter = rawDataFilter;
77 mRawDataFilterMask = rawDataFilterMask;
78 mRenamePrefix = renamePrefix;
79 mRenameSuffix = renameSuffix;
80 mRenameBytesFrom = renameBytesFrom;
81 mRenameBytesLength = renameBytesLength;
82 mRenameNameFrom = renameNameFrom;
83 mRenameNameLength = renameNameLength;
84 mRenameBytesReverseOrder = renameBytesReverseOrder;
89 public Pattern getNamePattern() {
95 public ScanFilter getScanFilter() {
101 public byte[] getRawDataFilter() {
102 return mRawDataFilter;
107 public byte[] getRawDataFilterMask() {
108 return mRawDataFilterMask;
113 public String getRenamePrefix() {
114 return mRenamePrefix;
119 public String getRenameSuffix() {
120 return mRenameSuffix;
124 public int getRenameBytesFrom() {
125 return mRenameBytesFrom;
129 public int getRenameBytesLength() {
130 return mRenameBytesLength;
134 public boolean isRenameBytesReverseOrder() {
135 return mRenameBytesReverseOrder;
141 public String getDeviceDisplayName(ScanResult sr) {
142 if (mRenameBytesFrom < 0 && mRenameNameFrom < 0) {
143 return getDeviceDisplayNameInternal(sr.getDevice());
145 final StringBuilder sb = new StringBuilder(TextUtils.emptyIfNull(mRenamePrefix));
146 if (mRenameBytesFrom >= 0) {
147 final byte[] bytes = sr.getScanRecord().getBytes();
148 int startInclusive = mRenameBytesFrom;
149 int endInclusive = mRenameBytesFrom + mRenameBytesLength -1;
150 int initial = mRenameBytesReverseOrder ? endInclusive : startInclusive;
151 int step = mRenameBytesReverseOrder ? -1 : 1;
152 for (int i = initial; startInclusive <= i && i <= endInclusive; i += step) {
153 sb.append(Byte.toHexString(bytes[i], true));
157 getDeviceDisplayNameInternal(sr.getDevice())
158 .substring(mRenameNameFrom, mRenameNameFrom + mRenameNameLength));
160 return sb.append(TextUtils.emptyIfNull(mRenameSuffix)).toString();
165 public boolean matches(ScanResult device) {
166 boolean result = matches(device.getDevice())
167 && (mRawDataFilter == null
168 || BitUtils.maskedEquals(device.getScanRecord().getBytes(),
169 mRawDataFilter, mRawDataFilterMask));
170 if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device +
175 private boolean matches(BluetoothDevice device) {
176 return BluetoothDeviceFilterUtils.matches(getScanFilter(), device)
177 && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device);
182 public int getMediumType() {
183 return DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE;
187 public boolean equals(Object o) {
188 if (this == o) return true;
189 if (o == null || getClass() != o.getClass()) return false;
190 BluetoothLeDeviceFilter that = (BluetoothLeDeviceFilter) o;
191 return mRenameBytesFrom == that.mRenameBytesFrom &&
192 mRenameBytesLength == that.mRenameBytesLength &&
193 mRenameNameFrom == that.mRenameNameFrom &&
194 mRenameNameLength == that.mRenameNameLength &&
195 mRenameBytesReverseOrder == that.mRenameBytesReverseOrder &&
196 Objects.equals(mNamePattern, that.mNamePattern) &&
197 Objects.equals(mScanFilter, that.mScanFilter) &&
198 Arrays.equals(mRawDataFilter, that.mRawDataFilter) &&
199 Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) &&
200 Objects.equals(mRenamePrefix, that.mRenamePrefix) &&
201 Objects.equals(mRenameSuffix, that.mRenameSuffix);
205 public int hashCode() {
206 return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask,
207 mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesLength,
208 mRenameNameFrom, mRenameNameLength, mRenameBytesReverseOrder);
212 public void writeToParcel(Parcel dest, int flags) {
213 dest.writeString(patternToString(getNamePattern()));
214 dest.writeParcelable(mScanFilter, flags);
215 dest.writeByteArray(mRawDataFilter);
216 dest.writeByteArray(mRawDataFilterMask);
217 dest.writeString(mRenamePrefix);
218 dest.writeString(mRenameSuffix);
219 dest.writeInt(mRenameBytesFrom);
220 dest.writeInt(mRenameBytesLength);
221 dest.writeInt(mRenameNameFrom);
222 dest.writeInt(mRenameNameLength);
223 dest.writeBoolean(mRenameBytesReverseOrder);
227 public int describeContents() {
232 public String toString() {
233 return "BluetoothLEDeviceFilter{" +
234 "mNamePattern=" + mNamePattern +
235 ", mScanFilter=" + mScanFilter +
236 ", mRawDataFilter=" + Arrays.toString(mRawDataFilter) +
237 ", mRawDataFilterMask=" + Arrays.toString(mRawDataFilterMask) +
238 ", mRenamePrefix='" + mRenamePrefix + '\'' +
239 ", mRenameSuffix='" + mRenameSuffix + '\'' +
240 ", mRenameBytesFrom=" + mRenameBytesFrom +
241 ", mRenameBytesLength=" + mRenameBytesLength +
242 ", mRenameNameFrom=" + mRenameNameFrom +
243 ", mRenameNameLength=" + mRenameNameLength +
244 ", mRenameBytesReverseOrder=" + mRenameBytesReverseOrder +
248 public static final Creator<BluetoothLeDeviceFilter> CREATOR
249 = new Creator<BluetoothLeDeviceFilter>() {
251 public BluetoothLeDeviceFilter createFromParcel(Parcel in) {
252 Builder builder = new Builder()
253 .setNamePattern(patternFromString(in.readString()))
254 .setScanFilter(in.readParcelable(null));
255 byte[] rawDataFilter = in.createByteArray();
256 byte[] rawDataFilterMask = in.createByteArray();
257 if (rawDataFilter != null) {
258 builder.setRawDataFilter(rawDataFilter, rawDataFilterMask);
260 String renamePrefix = in.readString();
261 String suffix = in.readString();
262 int bytesFrom = in.readInt();
263 int bytesTo = in.readInt();
264 int nameFrom = in.readInt();
265 int nameTo = in.readInt();
266 boolean bytesReverseOrder = in.readBoolean();
267 if (renamePrefix != null) {
268 if (bytesFrom >= 0) {
269 builder.setRenameFromBytes(renamePrefix, suffix, bytesFrom, bytesTo,
270 bytesReverseOrder ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
272 builder.setRenameFromName(renamePrefix, suffix, nameFrom, nameTo);
275 return builder.build();
279 public BluetoothLeDeviceFilter[] newArray(int size) {
280 return new BluetoothLeDeviceFilter[size];
284 public static int getRenamePrefixLengthLimit() {
285 return RENAME_PREFIX_LENGTH_LIMIT;
289 * Builder for {@link BluetoothLeDeviceFilter}
291 public static final class Builder extends OneTimeUseBuilder<BluetoothLeDeviceFilter> {
292 private ScanFilter mScanFilter;
293 private Pattern mNamePattern;
294 private byte[] mRawDataFilter;
295 private byte[] mRawDataFilterMask;
296 private String mRenamePrefix;
297 private String mRenameSuffix;
298 private int mRenameBytesFrom = -1;
299 private int mRenameBytesLength;
300 private int mRenameNameFrom = -1;
301 private int mRenameNameLength;
302 private boolean mRenameBytesReverseOrder = false;
305 * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the
306 * given regular expression will be shown
307 * @return self for chaining
309 public Builder setNamePattern(@Nullable Pattern regex) {
311 mNamePattern = regex;
316 * @param scanFilter a {@link ScanFilter} to filter devices by
318 * @return self for chaining
319 * @see ScanFilter for specific details on its various fields
322 public Builder setScanFilter(@Nullable ScanFilter scanFilter) {
324 mScanFilter = scanFilter;
329 * Filter devices by raw advertisement data, as obtained by {@link ScanRecord#getBytes}
331 * @param rawDataFilter bit values that have to match against advertized data
332 * @param rawDataFilterMask bits that have to be matched
333 * @return self for chaining
336 public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
337 @Nullable byte[] rawDataFilterMask) {
339 Preconditions.checkNotNull(rawDataFilter);
340 checkArgument(rawDataFilterMask == null ||
341 rawDataFilter.length == rawDataFilterMask.length,
342 "Mask and filter should be the same length");
343 mRawDataFilter = rawDataFilter;
344 mRawDataFilterMask = rawDataFilterMask;
349 * Rename the devices shown in the list, using specific bytes from the raw advertisement
350 * data ({@link ScanRecord#getBytes}) in hexadecimal format, as well as a custom
351 * prefix/suffix around them
353 * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters
354 * to ensure that there's enough space to display the byte data
356 * The range of bytes to be displayed cannot be empty
358 * @param prefix to be displayed before the byte data
359 * @param suffix to be displayed after the byte data
360 * @param bytesFrom the start byte index to be displayed (inclusive)
361 * @param bytesLength the number of bytes to be displayed from the given index
362 * @param byteOrder whether the given range of bytes is big endian (will be displayed
363 * in same order) or little endian (will be flipped before displaying)
364 * @return self for chaining
367 public Builder setRenameFromBytes(@NonNull String prefix, @NonNull String suffix,
368 int bytesFrom, int bytesLength, ByteOrder byteOrder) {
370 checkRangeNotEmpty(bytesLength);
371 mRenameBytesFrom = bytesFrom;
372 mRenameBytesLength = bytesLength;
373 mRenameBytesReverseOrder = byteOrder == ByteOrder.LITTLE_ENDIAN;
374 return setRename(prefix, suffix);
378 * Rename the devices shown in the list, using specific characters from the advertised name,
379 * as well as a custom prefix/suffix around them
381 * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters
382 * to ensure that there's enough space to display the byte data
384 * The range of name characters to be displayed cannot be empty
386 * @param prefix to be displayed before the byte data
387 * @param suffix to be displayed after the byte data
388 * @param nameFrom the start name character index to be displayed (inclusive)
389 * @param nameLength the number of characters to be displayed from the given index
390 * @return self for chaining
393 public Builder setRenameFromName(@NonNull String prefix, @NonNull String suffix,
394 int nameFrom, int nameLength) {
396 checkRangeNotEmpty(nameLength);
397 mRenameNameFrom = nameFrom;
398 mRenameNameLength = nameLength;
399 mRenameBytesReverseOrder = false;
400 return setRename(prefix, suffix);
403 private void checkRenameNotSet() {
404 checkState(mRenamePrefix == null, "Renaming rule can only be set once");
407 private void checkRangeNotEmpty(int length) {
408 checkArgument(length > 0, "Range must be non-empty");
412 private Builder setRename(@NonNull String prefix, @NonNull String suffix) {
414 checkArgument(TextUtils.length(prefix) <= getRenamePrefixLengthLimit(),
415 "Prefix is too long");
416 mRenamePrefix = prefix;
417 mRenameSuffix = suffix;
424 public BluetoothLeDeviceFilter build() {
426 return new BluetoothLeDeviceFilter(mNamePattern, mScanFilter,
427 mRawDataFilter, mRawDataFilterMask,
428 mRenamePrefix, mRenameSuffix,
429 mRenameBytesFrom, mRenameBytesLength,
430 mRenameNameFrom, mRenameNameLength,
431 mRenameBytesReverseOrder);