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.annotation.UnsupportedAppUsage; 29 import android.bluetooth.BluetoothDevice; 30 import android.bluetooth.le.ScanFilter; 31 import android.bluetooth.le.ScanRecord; 32 import android.bluetooth.le.ScanResult; 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 java.nio.ByteOrder; 43 import java.util.Arrays; 44 import java.util.Objects; 45 import java.util.regex.Pattern; 46 47 /** 48 * A filter for Bluetooth LE devices 49 * 50 * @see ScanFilter 51 */ 52 public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> { 53 54 private static final boolean DEBUG = false; 55 private static final String LOG_TAG = "BluetoothLeDeviceFilter"; 56 57 private static final int RENAME_PREFIX_LENGTH_LIMIT = 10; 58 59 private final Pattern mNamePattern; 60 private final ScanFilter mScanFilter; 61 private final byte[] mRawDataFilter; 62 private final byte[] mRawDataFilterMask; 63 private final String mRenamePrefix; 64 private final String mRenameSuffix; 65 private final int mRenameBytesFrom; 66 private final int mRenameBytesLength; 67 private final int mRenameNameFrom; 68 private final int mRenameNameLength; 69 private final boolean mRenameBytesReverseOrder; 70 BluetoothLeDeviceFilter(Pattern namePattern, ScanFilter scanFilter, byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix, String renameSuffix, int renameBytesFrom, int renameBytesLength, int renameNameFrom, int renameNameLength, boolean renameBytesReverseOrder)71 private BluetoothLeDeviceFilter(Pattern namePattern, ScanFilter scanFilter, 72 byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix, 73 String renameSuffix, int renameBytesFrom, int renameBytesLength, 74 int renameNameFrom, int renameNameLength, boolean renameBytesReverseOrder) { 75 mNamePattern = namePattern; 76 mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY); 77 mRawDataFilter = rawDataFilter; 78 mRawDataFilterMask = rawDataFilterMask; 79 mRenamePrefix = renamePrefix; 80 mRenameSuffix = renameSuffix; 81 mRenameBytesFrom = renameBytesFrom; 82 mRenameBytesLength = renameBytesLength; 83 mRenameNameFrom = renameNameFrom; 84 mRenameNameLength = renameNameLength; 85 mRenameBytesReverseOrder = renameBytesReverseOrder; 86 } 87 88 /** @hide */ 89 @Nullable getNamePattern()90 public Pattern getNamePattern() { 91 return mNamePattern; 92 } 93 94 /** @hide */ 95 @NonNull 96 @UnsupportedAppUsage getScanFilter()97 public ScanFilter getScanFilter() { 98 return mScanFilter; 99 } 100 101 /** @hide */ 102 @Nullable getRawDataFilter()103 public byte[] getRawDataFilter() { 104 return mRawDataFilter; 105 } 106 107 /** @hide */ 108 @Nullable getRawDataFilterMask()109 public byte[] getRawDataFilterMask() { 110 return mRawDataFilterMask; 111 } 112 113 /** @hide */ 114 @Nullable getRenamePrefix()115 public String getRenamePrefix() { 116 return mRenamePrefix; 117 } 118 119 /** @hide */ 120 @Nullable getRenameSuffix()121 public String getRenameSuffix() { 122 return mRenameSuffix; 123 } 124 125 /** @hide */ getRenameBytesFrom()126 public int getRenameBytesFrom() { 127 return mRenameBytesFrom; 128 } 129 130 /** @hide */ getRenameBytesLength()131 public int getRenameBytesLength() { 132 return mRenameBytesLength; 133 } 134 135 /** @hide */ isRenameBytesReverseOrder()136 public boolean isRenameBytesReverseOrder() { 137 return mRenameBytesReverseOrder; 138 } 139 140 /** @hide */ 141 @Override 142 @Nullable getDeviceDisplayName(ScanResult sr)143 public String getDeviceDisplayName(ScanResult sr) { 144 if (mRenameBytesFrom < 0 && mRenameNameFrom < 0) { 145 return getDeviceDisplayNameInternal(sr.getDevice()); 146 } 147 final StringBuilder sb = new StringBuilder(TextUtils.emptyIfNull(mRenamePrefix)); 148 if (mRenameBytesFrom >= 0) { 149 final byte[] bytes = sr.getScanRecord().getBytes(); 150 int startInclusive = mRenameBytesFrom; 151 int endInclusive = mRenameBytesFrom + mRenameBytesLength -1; 152 int initial = mRenameBytesReverseOrder ? endInclusive : startInclusive; 153 int step = mRenameBytesReverseOrder ? -1 : 1; 154 for (int i = initial; startInclusive <= i && i <= endInclusive; i += step) { 155 sb.append(Byte.toHexString(bytes[i], true)); 156 } 157 } else { 158 sb.append( 159 getDeviceDisplayNameInternal(sr.getDevice()) 160 .substring(mRenameNameFrom, mRenameNameFrom + mRenameNameLength)); 161 } 162 return sb.append(TextUtils.emptyIfNull(mRenameSuffix)).toString(); 163 } 164 165 /** @hide */ 166 @Override matches(ScanResult device)167 public boolean matches(ScanResult device) { 168 boolean result = matches(device.getDevice()) 169 && (mRawDataFilter == null 170 || BitUtils.maskedEquals(device.getScanRecord().getBytes(), 171 mRawDataFilter, mRawDataFilterMask)); 172 if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device + 173 ") -> " + result); 174 return result; 175 } 176 matches(BluetoothDevice device)177 private boolean matches(BluetoothDevice device) { 178 return BluetoothDeviceFilterUtils.matches(getScanFilter(), device) 179 && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device); 180 } 181 182 /** @hide */ 183 @Override getMediumType()184 public int getMediumType() { 185 return DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE; 186 } 187 188 @Override equals(Object o)189 public boolean equals(Object o) { 190 if (this == o) return true; 191 if (o == null || getClass() != o.getClass()) return false; 192 BluetoothLeDeviceFilter that = (BluetoothLeDeviceFilter) o; 193 return mRenameBytesFrom == that.mRenameBytesFrom && 194 mRenameBytesLength == that.mRenameBytesLength && 195 mRenameNameFrom == that.mRenameNameFrom && 196 mRenameNameLength == that.mRenameNameLength && 197 mRenameBytesReverseOrder == that.mRenameBytesReverseOrder && 198 Objects.equals(mNamePattern, that.mNamePattern) && 199 Objects.equals(mScanFilter, that.mScanFilter) && 200 Arrays.equals(mRawDataFilter, that.mRawDataFilter) && 201 Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) && 202 Objects.equals(mRenamePrefix, that.mRenamePrefix) && 203 Objects.equals(mRenameSuffix, that.mRenameSuffix); 204 } 205 206 @Override hashCode()207 public int hashCode() { 208 return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask, 209 mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesLength, 210 mRenameNameFrom, mRenameNameLength, 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)); 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 Preconditions.checkNotNull(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