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.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 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
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 device)169     public boolean matches(ScanResult device) {
170         boolean result = matches(device.getDevice())
171                 && (mRawDataFilter == null
172                     || BitUtils.maskedEquals(device.getScanRecord().getBytes(),
173                             mRawDataFilter, mRawDataFilterMask));
174         if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device +
175                 ") -> " + result);
176         return result;
177     }
178 
matches(BluetoothDevice device)179     private boolean matches(BluetoothDevice device) {
180         return BluetoothDeviceFilterUtils.matches(getScanFilter(), device)
181                 && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device);
182     }
183 
184     /** @hide */
185     @Override
getMediumType()186     public int getMediumType() {
187         return DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE;
188     }
189 
190     @Override
equals(Object o)191     public boolean equals(Object o) {
192         if (this == o) return true;
193         if (o == null || getClass() != o.getClass()) return false;
194         BluetoothLeDeviceFilter that = (BluetoothLeDeviceFilter) o;
195         return mRenameBytesFrom == that.mRenameBytesFrom &&
196                 mRenameBytesLength == that.mRenameBytesLength &&
197                 mRenameNameFrom == that.mRenameNameFrom &&
198                 mRenameNameLength == that.mRenameNameLength &&
199                 mRenameBytesReverseOrder == that.mRenameBytesReverseOrder &&
200                 Objects.equals(mNamePattern, that.mNamePattern) &&
201                 Objects.equals(mScanFilter, that.mScanFilter) &&
202                 Arrays.equals(mRawDataFilter, that.mRawDataFilter) &&
203                 Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) &&
204                 Objects.equals(mRenamePrefix, that.mRenamePrefix) &&
205                 Objects.equals(mRenameSuffix, that.mRenameSuffix);
206     }
207 
208     @Override
hashCode()209     public int hashCode() {
210         return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask,
211                 mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesLength,
212                 mRenameNameFrom, mRenameNameLength, mRenameBytesReverseOrder);
213     }
214 
215     @Override
writeToParcel(Parcel dest, int flags)216     public void writeToParcel(Parcel dest, int flags) {
217         dest.writeString(patternToString(getNamePattern()));
218         dest.writeParcelable(mScanFilter, flags);
219         dest.writeByteArray(mRawDataFilter);
220         dest.writeByteArray(mRawDataFilterMask);
221         dest.writeString(mRenamePrefix);
222         dest.writeString(mRenameSuffix);
223         dest.writeInt(mRenameBytesFrom);
224         dest.writeInt(mRenameBytesLength);
225         dest.writeInt(mRenameNameFrom);
226         dest.writeInt(mRenameNameLength);
227         dest.writeBoolean(mRenameBytesReverseOrder);
228     }
229 
230     @Override
describeContents()231     public int describeContents() {
232         return 0;
233     }
234 
235     @Override
toString()236     public String toString() {
237         return "BluetoothLEDeviceFilter{" +
238                 "mNamePattern=" + mNamePattern +
239                 ", mScanFilter=" + mScanFilter +
240                 ", mRawDataFilter=" + Arrays.toString(mRawDataFilter) +
241                 ", mRawDataFilterMask=" + Arrays.toString(mRawDataFilterMask) +
242                 ", mRenamePrefix='" + mRenamePrefix + '\'' +
243                 ", mRenameSuffix='" + mRenameSuffix + '\'' +
244                 ", mRenameBytesFrom=" + mRenameBytesFrom +
245                 ", mRenameBytesLength=" + mRenameBytesLength +
246                 ", mRenameNameFrom=" + mRenameNameFrom +
247                 ", mRenameNameLength=" + mRenameNameLength +
248                 ", mRenameBytesReverseOrder=" + mRenameBytesReverseOrder +
249                 '}';
250     }
251 
252     public static final @android.annotation.NonNull Creator<BluetoothLeDeviceFilter> CREATOR
253             = new Creator<BluetoothLeDeviceFilter>() {
254         @Override
255         public BluetoothLeDeviceFilter createFromParcel(Parcel in) {
256             Builder builder = new Builder()
257                     .setNamePattern(patternFromString(in.readString()))
258                     .setScanFilter(in.readParcelable(null));
259             byte[] rawDataFilter = in.createByteArray();
260             byte[] rawDataFilterMask = in.createByteArray();
261             if (rawDataFilter != null) {
262                 builder.setRawDataFilter(rawDataFilter, rawDataFilterMask);
263             }
264             String renamePrefix = in.readString();
265             String suffix = in.readString();
266             int bytesFrom = in.readInt();
267             int bytesTo = in.readInt();
268             int nameFrom = in.readInt();
269             int nameTo = in.readInt();
270             boolean bytesReverseOrder = in.readBoolean();
271             if (renamePrefix != null) {
272                 if (bytesFrom >= 0) {
273                     builder.setRenameFromBytes(renamePrefix, suffix, bytesFrom, bytesTo,
274                             bytesReverseOrder ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
275                 } else {
276                     builder.setRenameFromName(renamePrefix, suffix, nameFrom, nameTo);
277                 }
278             }
279             return builder.build();
280         }
281 
282         @Override
283         public BluetoothLeDeviceFilter[] newArray(int size) {
284             return new BluetoothLeDeviceFilter[size];
285         }
286     };
287 
getRenamePrefixLengthLimit()288     public static int getRenamePrefixLengthLimit() {
289         return RENAME_PREFIX_LENGTH_LIMIT;
290     }
291 
292     /**
293      * Builder for {@link BluetoothLeDeviceFilter}
294      */
295     public static final class Builder extends OneTimeUseBuilder<BluetoothLeDeviceFilter> {
296         private ScanFilter mScanFilter;
297         private Pattern mNamePattern;
298         private byte[] mRawDataFilter;
299         private byte[] mRawDataFilterMask;
300         private String mRenamePrefix;
301         private String mRenameSuffix;
302         private int mRenameBytesFrom = -1;
303         private int mRenameBytesLength;
304         private int mRenameNameFrom = -1;
305         private int mRenameNameLength;
306         private boolean mRenameBytesReverseOrder = false;
307 
308         /**
309          * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the
310          *              given regular expression will be shown
311          * @return self for chaining
312          */
setNamePattern(@ullable Pattern regex)313         public Builder setNamePattern(@Nullable Pattern regex) {
314             checkNotUsed();
315             mNamePattern = regex;
316             return this;
317         }
318 
319         /**
320          * @param scanFilter a {@link ScanFilter} to filter devices by
321          *
322          * @return self for chaining
323          * @see ScanFilter for specific details on its various fields
324          */
325         @NonNull
setScanFilter(@ullable ScanFilter scanFilter)326         public Builder setScanFilter(@Nullable ScanFilter scanFilter) {
327             checkNotUsed();
328             mScanFilter = scanFilter;
329             return this;
330         }
331 
332         /**
333          * Filter devices by raw advertisement data, as obtained by {@link ScanRecord#getBytes}
334          *
335          * @param rawDataFilter bit values that have to match against advertized data
336          * @param rawDataFilterMask bits that have to be matched
337          * @return self for chaining
338          */
339         @NonNull
setRawDataFilter(@onNull byte[] rawDataFilter, @Nullable byte[] rawDataFilterMask)340         public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
341                 @Nullable byte[] rawDataFilterMask) {
342             checkNotUsed();
343             Preconditions.checkNotNull(rawDataFilter);
344             checkArgument(rawDataFilterMask == null ||
345                     rawDataFilter.length == rawDataFilterMask.length,
346                     "Mask and filter should be the same length");
347             mRawDataFilter = rawDataFilter;
348             mRawDataFilterMask = rawDataFilterMask;
349             return this;
350         }
351 
352         /**
353          * Rename the devices shown in the list, using specific bytes from the raw advertisement
354          * data ({@link ScanRecord#getBytes}) in hexadecimal format, as well as a custom
355          * prefix/suffix around them
356          *
357          * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters
358          * to ensure that there's enough space to display the byte data
359          *
360          * The range of bytes to be displayed cannot be empty
361          *
362          * @param prefix to be displayed before the byte data
363          * @param suffix to be displayed after the byte data
364          * @param bytesFrom the start byte index to be displayed (inclusive)
365          * @param bytesLength the number of bytes to be displayed from the given index
366          * @param byteOrder whether the given range of bytes is big endian (will be displayed
367          *                   in same order) or little endian (will be flipped before displaying)
368          * @return self for chaining
369          */
370         @NonNull
setRenameFromBytes(@onNull String prefix, @NonNull String suffix, int bytesFrom, int bytesLength, ByteOrder byteOrder)371         public Builder setRenameFromBytes(@NonNull String prefix, @NonNull String suffix,
372                 int bytesFrom, int bytesLength, ByteOrder byteOrder) {
373             checkRenameNotSet();
374             checkRangeNotEmpty(bytesLength);
375             mRenameBytesFrom = bytesFrom;
376             mRenameBytesLength = bytesLength;
377             mRenameBytesReverseOrder = byteOrder == ByteOrder.LITTLE_ENDIAN;
378             return setRename(prefix, suffix);
379         }
380 
381         /**
382          * Rename the devices shown in the list, using specific characters from the advertised name,
383          * as well as a custom prefix/suffix around them
384          *
385          * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters
386          * to ensure that there's enough space to display the byte data
387          *
388          * The range of name characters to be displayed cannot be empty
389          *
390          * @param prefix to be displayed before the byte data
391          * @param suffix to be displayed after the byte data
392          * @param nameFrom the start name character index to be displayed (inclusive)
393          * @param nameLength the number of characters to be displayed from the given index
394          * @return self for chaining
395          */
396         @NonNull
setRenameFromName(@onNull String prefix, @NonNull String suffix, int nameFrom, int nameLength)397         public Builder setRenameFromName(@NonNull String prefix, @NonNull String suffix,
398                 int nameFrom, int nameLength) {
399             checkRenameNotSet();
400             checkRangeNotEmpty(nameLength);
401             mRenameNameFrom = nameFrom;
402             mRenameNameLength = nameLength;
403             mRenameBytesReverseOrder = false;
404             return setRename(prefix, suffix);
405         }
406 
checkRenameNotSet()407         private void checkRenameNotSet() {
408             checkState(mRenamePrefix == null, "Renaming rule can only be set once");
409         }
410 
checkRangeNotEmpty(int length)411         private void checkRangeNotEmpty(int length) {
412             checkArgument(length > 0, "Range must be non-empty");
413         }
414 
415         @NonNull
setRename(@onNull String prefix, @NonNull String suffix)416         private Builder setRename(@NonNull String prefix, @NonNull String suffix) {
417             checkNotUsed();
418             checkArgument(TextUtils.length(prefix) <= getRenamePrefixLengthLimit(),
419                     "Prefix is too long");
420             mRenamePrefix = prefix;
421             mRenameSuffix = suffix;
422             return this;
423         }
424 
425         /** @inheritDoc */
426         @Override
427         @NonNull
build()428         public BluetoothLeDeviceFilter build() {
429             markUsed();
430             return new BluetoothLeDeviceFilter(mNamePattern, mScanFilter,
431                     mRawDataFilter, mRawDataFilterMask,
432                     mRenamePrefix, mRenameSuffix,
433                     mRenameBytesFrom, mRenameBytesLength,
434                     mRenameNameFrom, mRenameNameLength,
435                     mRenameBytesReverseOrder);
436         }
437     }
438 }
439