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