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