blob: e5ea4e94b67ff3629d8a5b09950dab48788c4da2 [file] [log] [blame]
Eugene Susla6ed45d82017-01-22 13:52:51 -08001/*
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
17package android.companion;
18
Eugene Susla36e866b2017-02-23 18:24:39 -080019import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
Eugene Susla6ed45d82017-01-22 13:52:51 -080020import static android.companion.BluetoothDeviceFilterUtils.patternFromString;
21import static android.companion.BluetoothDeviceFilterUtils.patternToString;
22
Eugene Susla36e866b2017-02-23 18:24:39 -080023import static com.android.internal.util.Preconditions.checkArgument;
Eugene Susla75fb8212017-04-05 10:36:24 -070024import static com.android.internal.util.Preconditions.checkState;
Eugene Susla36e866b2017-02-23 18:24:39 -080025
Eugene Susla6ed45d82017-01-22 13:52:51 -080026import android.annotation.NonNull;
27import android.annotation.Nullable;
Eugene Susla6ed45d82017-01-22 13:52:51 -080028import android.bluetooth.BluetoothDevice;
29import android.bluetooth.le.ScanFilter;
Eugene Susla36e866b2017-02-23 18:24:39 -080030import android.bluetooth.le.ScanRecord;
31import android.bluetooth.le.ScanResult;
Eugene Susla6ed45d82017-01-22 13:52:51 -080032import android.os.Parcel;
33import android.provider.OneTimeUseBuilder;
Eugene Susla36e866b2017-02-23 18:24:39 -080034import android.text.TextUtils;
Eugene Susla3c9aa172017-03-28 15:04:12 -070035import android.util.Log;
Eugene Susla6ed45d82017-01-22 13:52:51 -080036
Eugene Susla36e866b2017-02-23 18:24:39 -080037import com.android.internal.util.BitUtils;
Eugene Susla6ed45d82017-01-22 13:52:51 -080038import com.android.internal.util.ObjectUtils;
Eugene Susla36e866b2017-02-23 18:24:39 -080039import com.android.internal.util.Preconditions;
Eugene Susla6ed45d82017-01-22 13:52:51 -080040
Eugene Suslaa38fbf62017-03-14 10:26:10 -070041import java.util.Arrays;
42import java.util.Objects;
Eugene Susla6ed45d82017-01-22 13:52:51 -080043import java.util.regex.Pattern;
44
45/**
46 * A filter for Bluetooth LE devices
47 *
48 * @see ScanFilter
49 */
Eugene Susla36e866b2017-02-23 18:24:39 -080050public final class BluetoothLEDeviceFilter implements DeviceFilter<ScanResult> {
Eugene Susla6ed45d82017-01-22 13:52:51 -080051
Eugene Susla3c9aa172017-03-28 15:04:12 -070052 private static final boolean DEBUG = false;
53 private static final String LOG_TAG = "BluetoothLEDeviceFilter";
54
Eugene Susla36e866b2017-02-23 18:24:39 -080055 private static final int RENAME_PREFIX_LENGTH_LIMIT = 10;
Eugene Susla6ed45d82017-01-22 13:52:51 -080056
57 private final Pattern mNamePattern;
58 private final ScanFilter mScanFilter;
Eugene Susla36e866b2017-02-23 18:24:39 -080059 private final byte[] mRawDataFilter;
60 private final byte[] mRawDataFilterMask;
61 private final String mRenamePrefix;
62 private final String mRenameSuffix;
63 private final int mRenameBytesFrom;
64 private final int mRenameBytesTo;
Eugene Susla75fb8212017-04-05 10:36:24 -070065 private final int mRenameNameFrom;
66 private final int mRenameNameTo;
Eugene Susla36e866b2017-02-23 18:24:39 -080067 private final boolean mRenameBytesReverseOrder;
Eugene Susla6ed45d82017-01-22 13:52:51 -080068
Eugene Susla36e866b2017-02-23 18:24:39 -080069 private BluetoothLEDeviceFilter(Pattern namePattern, ScanFilter scanFilter,
70 byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix,
71 String renameSuffix, int renameBytesFrom, int renameBytesTo,
Eugene Susla75fb8212017-04-05 10:36:24 -070072 int renameNameFrom, int renameNameTo, boolean renameBytesReverseOrder) {
Eugene Susla6ed45d82017-01-22 13:52:51 -080073 mNamePattern = namePattern;
74 mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY);
Eugene Susla36e866b2017-02-23 18:24:39 -080075 mRawDataFilter = rawDataFilter;
76 mRawDataFilterMask = rawDataFilterMask;
77 mRenamePrefix = renamePrefix;
78 mRenameSuffix = renameSuffix;
79 mRenameBytesFrom = renameBytesFrom;
80 mRenameBytesTo = renameBytesTo;
Eugene Susla75fb8212017-04-05 10:36:24 -070081 mRenameNameFrom = renameNameFrom;
82 mRenameNameTo = renameNameTo;
Eugene Susla36e866b2017-02-23 18:24:39 -080083 mRenameBytesReverseOrder = renameBytesReverseOrder;
Eugene Susla6ed45d82017-01-22 13:52:51 -080084 }
85
86 /** @hide */
87 @Nullable
88 public Pattern getNamePattern() {
89 return mNamePattern;
90 }
91
92 /** @hide */
93 @NonNull
94 public ScanFilter getScanFilter() {
95 return mScanFilter;
96 }
97
98 /** @hide */
Eugene Susla36e866b2017-02-23 18:24:39 -080099 @Nullable
100 public byte[] getRawDataFilter() {
101 return mRawDataFilter;
102 }
103
104 /** @hide */
105 @Nullable
106 public byte[] getRawDataFilterMask() {
107 return mRawDataFilterMask;
108 }
109
110 /** @hide */
111 @Nullable
112 public String getRenamePrefix() {
113 return mRenamePrefix;
114 }
115
116 /** @hide */
117 @Nullable
118 public String getRenameSuffix() {
119 return mRenameSuffix;
120 }
121
122 /** @hide */
123 public int getRenameBytesFrom() {
124 return mRenameBytesFrom;
125 }
126
127 /** @hide */
128 public int getRenameBytesTo() {
129 return mRenameBytesTo;
130 }
131
132 /** @hide */
133 public boolean isRenameBytesReverseOrder() {
134 return mRenameBytesReverseOrder;
135 }
136
137 /** @hide */
Eugene Susla6ed45d82017-01-22 13:52:51 -0800138 @Override
Eugene Susla36e866b2017-02-23 18:24:39 -0800139 @Nullable
140 public String getDeviceDisplayName(ScanResult sr) {
Eugene Susla75fb8212017-04-05 10:36:24 -0700141 if (mRenameBytesFrom < 0 && mRenameNameFrom < 0) {
142 return getDeviceDisplayNameInternal(sr.getDevice());
143 }
Eugene Susla36e866b2017-02-23 18:24:39 -0800144 final StringBuilder sb = new StringBuilder(TextUtils.emptyIfNull(mRenamePrefix));
Eugene Susla75fb8212017-04-05 10:36:24 -0700145 if (mRenameBytesFrom >= 0) {
146 final byte[] bytes = sr.getScanRecord().getBytes();
147 int startInclusive = mRenameBytesFrom;
148 int endInclusive = mRenameBytesTo - 1;
149 int initial = mRenameBytesReverseOrder ? endInclusive : startInclusive;
150 int step = mRenameBytesReverseOrder ? -1 : 1;
151 for (int i = initial; startInclusive <= i && i <= endInclusive; i += step) {
152 sb.append(Byte.toHexString(bytes[i], true));
153 }
154 } else {
155 sb.append(
156 getDeviceDisplayNameInternal(sr.getDevice())
157 .substring(mRenameNameFrom, mRenameNameTo));
Eugene Susla36e866b2017-02-23 18:24:39 -0800158 }
159 return sb.append(TextUtils.emptyIfNull(mRenameSuffix)).toString();
160 }
161
162 /** @hide */
163 @Override
164 public boolean matches(ScanResult device) {
Eugene Susla3c9aa172017-03-28 15:04:12 -0700165 boolean result = matches(device.getDevice())
166 && (mRawDataFilter == null
167 || BitUtils.maskedEquals(device.getScanRecord().getBytes(),
168 mRawDataFilter, mRawDataFilterMask));
169 if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device +
170 ") -> " + result);
171 return result;
Eugene Susla36e866b2017-02-23 18:24:39 -0800172 }
173
174 private boolean matches(BluetoothDevice device) {
Eugene Susla6ed45d82017-01-22 13:52:51 -0800175 return BluetoothDeviceFilterUtils.matches(getScanFilter(), device)
176 && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device);
177 }
178
Eugene Susla36e866b2017-02-23 18:24:39 -0800179 /** @hide */
180 @Override
181 public int getMediumType() {
182 return DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE;
183 }
184
Eugene Susla6ed45d82017-01-22 13:52:51 -0800185 @Override
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700186 public boolean equals(Object o) {
187 if (this == o) return true;
188 if (o == null || getClass() != o.getClass()) return false;
189 BluetoothLEDeviceFilter that = (BluetoothLEDeviceFilter) o;
190 return mRenameBytesFrom == that.mRenameBytesFrom &&
191 mRenameBytesTo == that.mRenameBytesTo &&
192 mRenameBytesReverseOrder == that.mRenameBytesReverseOrder &&
193 Objects.equals(mNamePattern, that.mNamePattern) &&
194 Objects.equals(mScanFilter, that.mScanFilter) &&
195 Arrays.equals(mRawDataFilter, that.mRawDataFilter) &&
196 Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) &&
197 Objects.equals(mRenamePrefix, that.mRenamePrefix) &&
198 Objects.equals(mRenameSuffix, that.mRenameSuffix);
199 }
200
201 @Override
202 public int hashCode() {
203 return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask,
204 mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesTo,
205 mRenameBytesReverseOrder);
206 }
207
208 @Override
Eugene Susla6ed45d82017-01-22 13:52:51 -0800209 public void writeToParcel(Parcel dest, int flags) {
210 dest.writeString(patternToString(getNamePattern()));
211 dest.writeParcelable(mScanFilter, flags);
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700212 dest.writeByteArray(mRawDataFilter);
213 dest.writeByteArray(mRawDataFilterMask);
214 dest.writeString(mRenamePrefix);
215 dest.writeString(mRenameSuffix);
216 dest.writeInt(mRenameBytesFrom);
217 dest.writeInt(mRenameBytesTo);
Eugene Susla75fb8212017-04-05 10:36:24 -0700218 dest.writeInt(mRenameNameFrom);
219 dest.writeInt(mRenameNameTo);
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700220 dest.writeBoolean(mRenameBytesReverseOrder);
Eugene Susla6ed45d82017-01-22 13:52:51 -0800221 }
222
223 @Override
224 public int describeContents() {
225 return 0;
226 }
227
Eugene Suslaa7717e32017-04-17 19:13:31 -0700228 @Override
229 public String toString() {
230 return "BluetoothLEDeviceFilter{" +
231 "mNamePattern=" + mNamePattern +
232 ", mScanFilter=" + mScanFilter +
233 ", mRawDataFilter=" + Arrays.toString(mRawDataFilter) +
234 ", mRawDataFilterMask=" + Arrays.toString(mRawDataFilterMask) +
235 ", mRenamePrefix='" + mRenamePrefix + '\'' +
236 ", mRenameSuffix='" + mRenameSuffix + '\'' +
237 ", mRenameBytesFrom=" + mRenameBytesFrom +
238 ", mRenameBytesTo=" + mRenameBytesTo +
239 ", mRenameNameFrom=" + mRenameNameFrom +
240 ", mRenameNameTo=" + mRenameNameTo +
241 ", mRenameBytesReverseOrder=" + mRenameBytesReverseOrder +
242 '}';
243 }
244
Eugene Susla6ed45d82017-01-22 13:52:51 -0800245 public static final Creator<BluetoothLEDeviceFilter> CREATOR
246 = new Creator<BluetoothLEDeviceFilter>() {
247 @Override
248 public BluetoothLEDeviceFilter createFromParcel(Parcel in) {
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700249 Builder builder = new Builder()
Eugene Susla36e866b2017-02-23 18:24:39 -0800250 .setNamePattern(patternFromString(in.readString()))
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700251 .setScanFilter(in.readParcelable(null));
252 byte[] rawDataFilter = in.createByteArray();
253 byte[] rawDataFilterMask = in.createByteArray();
254 if (rawDataFilter != null) {
255 builder.setRawDataFilter(rawDataFilter, rawDataFilterMask);
256 }
257 String renamePrefix = in.readString();
258 String suffix = in.readString();
259 int bytesFrom = in.readInt();
260 int bytesTo = in.readInt();
Eugene Susla75fb8212017-04-05 10:36:24 -0700261 int nameFrom = in.readInt();
262 int nameTo = in.readInt();
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700263 boolean bytesReverseOrder = in.readBoolean();
264 if (renamePrefix != null) {
Eugene Susla75fb8212017-04-05 10:36:24 -0700265 if (bytesFrom >= 0) {
266 builder.setRenameFromBytes(renamePrefix, suffix, bytesFrom, bytesTo,
267 bytesReverseOrder);
268 } else {
269 builder.setRenameFromName(renamePrefix, suffix, nameFrom, nameTo);
270 }
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700271 }
272 return builder.build();
Eugene Susla6ed45d82017-01-22 13:52:51 -0800273 }
274
275 @Override
276 public BluetoothLEDeviceFilter[] newArray(int size) {
277 return new BluetoothLEDeviceFilter[size];
278 }
279 };
280
Eugene Susla36e866b2017-02-23 18:24:39 -0800281 public static int getRenamePrefixLengthLimit() {
282 return RENAME_PREFIX_LENGTH_LIMIT;
283 }
284
Eugene Susla6ed45d82017-01-22 13:52:51 -0800285 /**
286 * Builder for {@link BluetoothLEDeviceFilter}
287 */
288 public static final class Builder extends OneTimeUseBuilder<BluetoothLEDeviceFilter> {
289 private ScanFilter mScanFilter;
290 private Pattern mNamePattern;
Eugene Susla36e866b2017-02-23 18:24:39 -0800291 private byte[] mRawDataFilter;
292 private byte[] mRawDataFilterMask;
293 private String mRenamePrefix;
294 private String mRenameSuffix;
295 private int mRenameBytesFrom = -1;
296 private int mRenameBytesTo;
Eugene Susla75fb8212017-04-05 10:36:24 -0700297 private int mRenameNameFrom = -1;
298 private int mRenameNameTo;
Eugene Susla36e866b2017-02-23 18:24:39 -0800299 private boolean mRenameBytesReverseOrder = false;
Eugene Susla6ed45d82017-01-22 13:52:51 -0800300
301 /**
302 * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the
303 * given regular expression will be shown
Eugene Susla36e866b2017-02-23 18:24:39 -0800304 * @return self for chaining
Eugene Susla6ed45d82017-01-22 13:52:51 -0800305 */
306 public Builder setNamePattern(@Nullable Pattern regex) {
307 checkNotUsed();
308 mNamePattern = regex;
309 return this;
310 }
311
312 /**
313 * @param scanFilter a {@link ScanFilter} to filter devices by
314 *
Eugene Susla36e866b2017-02-23 18:24:39 -0800315 * @return self for chaining
Eugene Susla6ed45d82017-01-22 13:52:51 -0800316 * @see ScanFilter for specific details on its various fields
317 */
318 @NonNull
319 public Builder setScanFilter(@Nullable ScanFilter scanFilter) {
320 checkNotUsed();
321 mScanFilter = scanFilter;
322 return this;
323 }
324
Eugene Susla36e866b2017-02-23 18:24:39 -0800325 /**
326 * Filter devices by raw advertisement data, as obtained by {@link ScanRecord#getBytes}
327 *
328 * @param rawDataFilter bit values that have to match against advertized data
329 * @param rawDataFilterMask bits that have to be matched
330 * @return self for chaining
331 */
332 @NonNull
333 public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700334 @Nullable byte[] rawDataFilterMask) {
Eugene Susla36e866b2017-02-23 18:24:39 -0800335 checkNotUsed();
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700336 Preconditions.checkNotNull(rawDataFilter);
337 checkArgument(rawDataFilterMask == null ||
338 rawDataFilter.length == rawDataFilterMask.length,
Eugene Susla36e866b2017-02-23 18:24:39 -0800339 "Mask and filter should be the same length");
Eugene Suslaa38fbf62017-03-14 10:26:10 -0700340 mRawDataFilter = rawDataFilter;
341 mRawDataFilterMask = rawDataFilterMask;
Eugene Susla36e866b2017-02-23 18:24:39 -0800342 return this;
343 }
344
345 /**
346 * Rename the devices shown in the list, using specific bytes from the raw advertisement
347 * data ({@link ScanRecord#getBytes}) in hexadecimal format, as well as a custom
348 * prefix/suffix around them
349 *
350 * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters
351 * to ensure that there's enough space to display the byte data
352 *
353 * The range of bytes to be displayed cannot be empty
354 *
355 * @param prefix to be displayed before the byte data
356 * @param suffix to be displayed after the byte data
357 * @param bytesFrom the start byte index to be displayed (inclusive)
358 * @param bytesTo the end byte index to be displayed (exclusive)
359 * @param bytesReverseOrder if true, the byte order of the provided range will be flipped
360 * when displaying
361 * @return self for chaining
362 */
363 @NonNull
Eugene Susla75fb8212017-04-05 10:36:24 -0700364 public Builder setRenameFromBytes(@NonNull String prefix, @NonNull String suffix,
Eugene Susla36e866b2017-02-23 18:24:39 -0800365 int bytesFrom, int bytesTo, boolean bytesReverseOrder) {
Eugene Susla75fb8212017-04-05 10:36:24 -0700366 checkRenameNotSet();
367 checkRangeNotEmpty(bytesFrom, bytesTo);
Eugene Susla36e866b2017-02-23 18:24:39 -0800368 mRenameBytesFrom = bytesFrom;
369 mRenameBytesTo = bytesTo;
370 mRenameBytesReverseOrder = bytesReverseOrder;
Eugene Susla75fb8212017-04-05 10:36:24 -0700371 return setRename(prefix, suffix);
372 }
373
374 /**
375 * Rename the devices shown in the list, using specific characters from the advertised name,
376 * as well as a custom prefix/suffix around them
377 *
378 * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters
379 * to ensure that there's enough space to display the byte data
380 *
381 * The range of name characters to be displayed cannot be empty
382 *
383 * @param prefix to be displayed before the byte data
384 * @param suffix to be displayed after the byte data
385 * @param nameFrom the start name character index to be displayed (inclusive)
386 * @param nameTo the end name character index to be displayed (exclusive)
387 * @return self for chaining
388 */
389 @NonNull
390 public Builder setRenameFromName(@NonNull String prefix, @NonNull String suffix,
391 int nameFrom, int nameTo) {
392 checkRenameNotSet();
393 checkRangeNotEmpty(nameFrom, nameTo);
394 mRenameNameFrom = nameFrom;
395 mRenameNameTo = nameTo;
396 mRenameBytesReverseOrder = false;
397 return setRename(prefix, suffix);
398 }
399
400 private void checkRenameNotSet() {
401 checkState(mRenamePrefix == null, "Renaming rule can only be set once");
402 }
403
404 private void checkRangeNotEmpty(int bytesFrom, int bytesTo) {
405 checkArgument(bytesFrom < bytesTo, "Range must be non-empty");
406 }
407
408 @NonNull
409 private Builder setRename(@NonNull String prefix, @NonNull String suffix) {
410 checkNotUsed();
411 checkArgument(TextUtils.length(prefix) <= getRenamePrefixLengthLimit(),
412 "Prefix is too long");
413 mRenamePrefix = prefix;
414 mRenameSuffix = suffix;
Eugene Susla36e866b2017-02-23 18:24:39 -0800415 return this;
416 }
417
Eugene Susla6ed45d82017-01-22 13:52:51 -0800418 /** @inheritDoc */
419 @Override
420 @NonNull
421 public BluetoothLEDeviceFilter build() {
422 markUsed();
Eugene Susla36e866b2017-02-23 18:24:39 -0800423 return new BluetoothLEDeviceFilter(mNamePattern, mScanFilter,
424 mRawDataFilter, mRawDataFilterMask,
425 mRenamePrefix, mRenameSuffix,
Eugene Susla75fb8212017-04-05 10:36:24 -0700426 mRenameBytesFrom, mRenameBytesTo,
427 mRenameNameFrom, mRenameNameTo,
428 mRenameBytesReverseOrder);
Eugene Susla6ed45d82017-01-22 13:52:51 -0800429 }
430 }
431}