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.os.Parcel; 33 import android.provider.OneTimeUseBuilder; 34 import android.text.TextUtils; 35 import android.util.Log; 36 37 import com.android.internal.util.BitUtils; 38 import com.android.internal.util.ObjectUtils; 39 import com.android.internal.util.Preconditions; 40 41 import java.nio.ByteOrder; 42 import java.util.Arrays; 43 import java.util.Objects; 44 import java.util.regex.Pattern; 45 46 /** 47 * A filter for Bluetooth LE devices 48 * 49 * @see ScanFilter 50 */ 51 public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> { 52 53 private static final boolean DEBUG = false; 54 private static final String LOG_TAG = "BluetoothLeDeviceFilter"; 55 56 private static final int RENAME_PREFIX_LENGTH_LIMIT = 10; 57 58 private final Pattern mNamePattern; 59 private final ScanFilter mScanFilter; 60 private final byte[] mRawDataFilter; 61 private final byte[] mRawDataFilterMask; 62 private final String mRenamePrefix; 63 private final String mRenameSuffix; 64 private final int mRenameBytesFrom; 65 private final int mRenameBytesLength; 66 private final int mRenameNameFrom; 67 private final int mRenameNameLength; 68 private final boolean mRenameBytesReverseOrder; 69 70 private BluetoothLeDeviceFilter(Pattern namePattern, ScanFilter scanFilter, 71 byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix, 72 String renameSuffix, int renameBytesFrom, int renameBytesLength, 73 int renameNameFrom, int renameNameLength, boolean renameBytesReverseOrder) { 74 mNamePattern = namePattern; 75 mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY); 76 mRawDataFilter = rawDataFilter; 77 mRawDataFilterMask = rawDataFilterMask; 78 mRenamePrefix = renamePrefix; 79 mRenameSuffix = renameSuffix; 80 mRenameBytesFrom = renameBytesFrom; 81 mRenameBytesLength = renameBytesLength; 82 mRenameNameFrom = renameNameFrom; 83 mRenameNameLength = renameNameLength; 84 mRenameBytesReverseOrder = renameBytesReverseOrder; 85 } 86 87 /** @hide */ 88 @Nullable 89 public Pattern getNamePattern() { 90 return mNamePattern; 91 } 92 93 /** @hide */ 94 @NonNull 95 public ScanFilter getScanFilter() { 96 return mScanFilter; 97 } 98 99 /** @hide */ 100 @Nullable 101 public byte[] getRawDataFilter() { 102 return mRawDataFilter; 103 } 104 105 /** @hide */ 106 @Nullable 107 public byte[] getRawDataFilterMask() { 108 return mRawDataFilterMask; 109 } 110 111 /** @hide */ 112 @Nullable 113 public String getRenamePrefix() { 114 return mRenamePrefix; 115 } 116 117 /** @hide */ 118 @Nullable 119 public String getRenameSuffix() { 120 return mRenameSuffix; 121 } 122 123 /** @hide */ 124 public int getRenameBytesFrom() { 125 return mRenameBytesFrom; 126 } 127 128 /** @hide */ 129 public int getRenameBytesLength() { 130 return mRenameBytesLength; 131 } 132 133 /** @hide */ 134 public boolean isRenameBytesReverseOrder() { 135 return mRenameBytesReverseOrder; 136 } 137 138 /** @hide */ 139 @Override 140 @Nullable 141 public String getDeviceDisplayName(ScanResult sr) { 142 if (mRenameBytesFrom < 0 && mRenameNameFrom < 0) { 143 return getDeviceDisplayNameInternal(sr.getDevice()); 144 } 145 final StringBuilder sb = new StringBuilder(TextUtils.emptyIfNull(mRenamePrefix)); 146 if (mRenameBytesFrom >= 0) { 147 final byte[] bytes = sr.getScanRecord().getBytes(); 148 int startInclusive = mRenameBytesFrom; 149 int endInclusive = mRenameBytesFrom + mRenameBytesLength -1; 150 int initial = mRenameBytesReverseOrder ? endInclusive : startInclusive; 151 int step = mRenameBytesReverseOrder ? -1 : 1; 152 for (int i = initial; startInclusive <= i && i <= endInclusive; i += step) { 153 sb.append(Byte.toHexString(bytes[i], true)); 154 } 155 } else { 156 sb.append( 157 getDeviceDisplayNameInternal(sr.getDevice()) 158 .substring(mRenameNameFrom, mRenameNameFrom + mRenameNameLength)); 159 } 160 return sb.append(TextUtils.emptyIfNull(mRenameSuffix)).toString(); 161 } 162 163 /** @hide */ 164 @Override 165 public boolean matches(ScanResult device) { 166 boolean result = matches(device.getDevice()) 167 && (mRawDataFilter == null 168 || BitUtils.maskedEquals(device.getScanRecord().getBytes(), 169 mRawDataFilter, mRawDataFilterMask)); 170 if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device + 171 ") -> " + result); 172 return result; 173 } 174 175 private boolean matches(BluetoothDevice device) { 176 return BluetoothDeviceFilterUtils.matches(getScanFilter(), device) 177 && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device); 178 } 179 180 /** @hide */ 181 @Override 182 public int getMediumType() { 183 return DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE; 184 } 185 186 @Override 187 public boolean equals(Object o) { 188 if (this == o) return true; 189 if (o == null || getClass() != o.getClass()) return false; 190 BluetoothLeDeviceFilter that = (BluetoothLeDeviceFilter) o; 191 return mRenameBytesFrom == that.mRenameBytesFrom && 192 mRenameBytesLength == that.mRenameBytesLength && 193 mRenameNameFrom == that.mRenameNameFrom && 194 mRenameNameLength == that.mRenameNameLength && 195 mRenameBytesReverseOrder == that.mRenameBytesReverseOrder && 196 Objects.equals(mNamePattern, that.mNamePattern) && 197 Objects.equals(mScanFilter, that.mScanFilter) && 198 Arrays.equals(mRawDataFilter, that.mRawDataFilter) && 199 Arrays.equals(mRawDataFilterMask, that.mRawDataFilterMask) && 200 Objects.equals(mRenamePrefix, that.mRenamePrefix) && 201 Objects.equals(mRenameSuffix, that.mRenameSuffix); 202 } 203 204 @Override 205 public int hashCode() { 206 return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask, 207 mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesLength, 208 mRenameNameFrom, mRenameNameLength, mRenameBytesReverseOrder); 209 } 210 211 @Override 212 public void writeToParcel(Parcel dest, int flags) { 213 dest.writeString(patternToString(getNamePattern())); 214 dest.writeParcelable(mScanFilter, flags); 215 dest.writeByteArray(mRawDataFilter); 216 dest.writeByteArray(mRawDataFilterMask); 217 dest.writeString(mRenamePrefix); 218 dest.writeString(mRenameSuffix); 219 dest.writeInt(mRenameBytesFrom); 220 dest.writeInt(mRenameBytesLength); 221 dest.writeInt(mRenameNameFrom); 222 dest.writeInt(mRenameNameLength); 223 dest.writeBoolean(mRenameBytesReverseOrder); 224 } 225 226 @Override 227 public int describeContents() { 228 return 0; 229 } 230 231 @Override 232 public String toString() { 233 return "BluetoothLEDeviceFilter{" + 234 "mNamePattern=" + mNamePattern + 235 ", mScanFilter=" + mScanFilter + 236 ", mRawDataFilter=" + Arrays.toString(mRawDataFilter) + 237 ", mRawDataFilterMask=" + Arrays.toString(mRawDataFilterMask) + 238 ", mRenamePrefix='" + mRenamePrefix + '\'' + 239 ", mRenameSuffix='" + mRenameSuffix + '\'' + 240 ", mRenameBytesFrom=" + mRenameBytesFrom + 241 ", mRenameBytesLength=" + mRenameBytesLength + 242 ", mRenameNameFrom=" + mRenameNameFrom + 243 ", mRenameNameLength=" + mRenameNameLength + 244 ", mRenameBytesReverseOrder=" + mRenameBytesReverseOrder + 245 '}'; 246 } 247 248 public static final Creator<BluetoothLeDeviceFilter> CREATOR 249 = new Creator<BluetoothLeDeviceFilter>() { 250 @Override 251 public BluetoothLeDeviceFilter createFromParcel(Parcel in) { 252 Builder builder = new Builder() 253 .setNamePattern(patternFromString(in.readString())) 254 .setScanFilter(in.readParcelable(null)); 255 byte[] rawDataFilter = in.createByteArray(); 256 byte[] rawDataFilterMask = in.createByteArray(); 257 if (rawDataFilter != null) { 258 builder.setRawDataFilter(rawDataFilter, rawDataFilterMask); 259 } 260 String renamePrefix = in.readString(); 261 String suffix = in.readString(); 262 int bytesFrom = in.readInt(); 263 int bytesTo = in.readInt(); 264 int nameFrom = in.readInt(); 265 int nameTo = in.readInt(); 266 boolean bytesReverseOrder = in.readBoolean(); 267 if (renamePrefix != null) { 268 if (bytesFrom >= 0) { 269 builder.setRenameFromBytes(renamePrefix, suffix, bytesFrom, bytesTo, 270 bytesReverseOrder ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); 271 } else { 272 builder.setRenameFromName(renamePrefix, suffix, nameFrom, nameTo); 273 } 274 } 275 return builder.build(); 276 } 277 278 @Override 279 public BluetoothLeDeviceFilter[] newArray(int size) { 280 return new BluetoothLeDeviceFilter[size]; 281 } 282 }; 283 284 public static int getRenamePrefixLengthLimit() { 285 return RENAME_PREFIX_LENGTH_LIMIT; 286 } 287 288 /** 289 * Builder for {@link BluetoothLeDeviceFilter} 290 */ 291 public static final class Builder extends OneTimeUseBuilder<BluetoothLeDeviceFilter> { 292 private ScanFilter mScanFilter; 293 private Pattern mNamePattern; 294 private byte[] mRawDataFilter; 295 private byte[] mRawDataFilterMask; 296 private String mRenamePrefix; 297 private String mRenameSuffix; 298 private int mRenameBytesFrom = -1; 299 private int mRenameBytesLength; 300 private int mRenameNameFrom = -1; 301 private int mRenameNameLength; 302 private boolean mRenameBytesReverseOrder = false; 303 304 /** 305 * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the 306 * given regular expression will be shown 307 * @return self for chaining 308 */ 309 public Builder setNamePattern(@Nullable Pattern regex) { 310 checkNotUsed(); 311 mNamePattern = regex; 312 return this; 313 } 314 315 /** 316 * @param scanFilter a {@link ScanFilter} to filter devices by 317 * 318 * @return self for chaining 319 * @see ScanFilter for specific details on its various fields 320 */ 321 @NonNull 322 public Builder setScanFilter(@Nullable ScanFilter scanFilter) { 323 checkNotUsed(); 324 mScanFilter = scanFilter; 325 return this; 326 } 327 328 /** 329 * Filter devices by raw advertisement data, as obtained by {@link ScanRecord#getBytes} 330 * 331 * @param rawDataFilter bit values that have to match against advertized data 332 * @param rawDataFilterMask bits that have to be matched 333 * @return self for chaining 334 */ 335 @NonNull 336 public Builder setRawDataFilter(@NonNull byte[] rawDataFilter, 337 @Nullable byte[] rawDataFilterMask) { 338 checkNotUsed(); 339 Preconditions.checkNotNull(rawDataFilter); 340 checkArgument(rawDataFilterMask == null || 341 rawDataFilter.length == rawDataFilterMask.length, 342 "Mask and filter should be the same length"); 343 mRawDataFilter = rawDataFilter; 344 mRawDataFilterMask = rawDataFilterMask; 345 return this; 346 } 347 348 /** 349 * Rename the devices shown in the list, using specific bytes from the raw advertisement 350 * data ({@link ScanRecord#getBytes}) in hexadecimal format, as well as a custom 351 * prefix/suffix around them 352 * 353 * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters 354 * to ensure that there's enough space to display the byte data 355 * 356 * The range of bytes to be displayed cannot be empty 357 * 358 * @param prefix to be displayed before the byte data 359 * @param suffix to be displayed after the byte data 360 * @param bytesFrom the start byte index to be displayed (inclusive) 361 * @param bytesLength the number of bytes to be displayed from the given index 362 * @param byteOrder whether the given range of bytes is big endian (will be displayed 363 * in same order) or little endian (will be flipped before displaying) 364 * @return self for chaining 365 */ 366 @NonNull 367 public Builder setRenameFromBytes(@NonNull String prefix, @NonNull String suffix, 368 int bytesFrom, int bytesLength, ByteOrder byteOrder) { 369 checkRenameNotSet(); 370 checkRangeNotEmpty(bytesLength); 371 mRenameBytesFrom = bytesFrom; 372 mRenameBytesLength = bytesLength; 373 mRenameBytesReverseOrder = byteOrder == ByteOrder.LITTLE_ENDIAN; 374 return setRename(prefix, suffix); 375 } 376 377 /** 378 * Rename the devices shown in the list, using specific characters from the advertised name, 379 * as well as a custom prefix/suffix around them 380 * 381 * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters 382 * to ensure that there's enough space to display the byte data 383 * 384 * The range of name characters to be displayed cannot be empty 385 * 386 * @param prefix to be displayed before the byte data 387 * @param suffix to be displayed after the byte data 388 * @param nameFrom the start name character index to be displayed (inclusive) 389 * @param nameLength the number of characters to be displayed from the given index 390 * @return self for chaining 391 */ 392 @NonNull 393 public Builder setRenameFromName(@NonNull String prefix, @NonNull String suffix, 394 int nameFrom, int nameLength) { 395 checkRenameNotSet(); 396 checkRangeNotEmpty(nameLength); 397 mRenameNameFrom = nameFrom; 398 mRenameNameLength = nameLength; 399 mRenameBytesReverseOrder = false; 400 return setRename(prefix, suffix); 401 } 402 403 private void checkRenameNotSet() { 404 checkState(mRenamePrefix == null, "Renaming rule can only be set once"); 405 } 406 407 private void checkRangeNotEmpty(int length) { 408 checkArgument(length > 0, "Range must be non-empty"); 409 } 410 411 @NonNull 412 private Builder setRename(@NonNull String prefix, @NonNull String suffix) { 413 checkNotUsed(); 414 checkArgument(TextUtils.length(prefix) <= getRenamePrefixLengthLimit(), 415 "Prefix is too long"); 416 mRenamePrefix = prefix; 417 mRenameSuffix = suffix; 418 return this; 419 } 420 421 /** @inheritDoc */ 422 @Override 423 @NonNull 424 public BluetoothLeDeviceFilter build() { 425 markUsed(); 426 return new BluetoothLeDeviceFilter(mNamePattern, mScanFilter, 427 mRawDataFilter, mRawDataFilterMask, 428 mRenamePrefix, mRenameSuffix, 429 mRenameBytesFrom, mRenameBytesLength, 430 mRenameNameFrom, mRenameNameLength, 431 mRenameBytesReverseOrder); 432 } 433 } 434 } 435