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