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