Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 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 | |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 19 | import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 20 | import static android.companion.BluetoothDeviceFilterUtils.patternFromString; |
| 21 | import static android.companion.BluetoothDeviceFilterUtils.patternToString; |
| 22 | |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 23 | import static com.android.internal.util.Preconditions.checkArgument; |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 24 | import static com.android.internal.util.Preconditions.checkState; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 25 | |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 26 | import android.annotation.NonNull; |
| 27 | import android.annotation.Nullable; |
Mathew Inwood | 70e89d5 | 2018-08-09 15:27:52 +0100 | [diff] [blame] | 28 | import android.annotation.UnsupportedAppUsage; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 29 | import android.bluetooth.BluetoothDevice; |
| 30 | import android.bluetooth.le.ScanFilter; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 31 | import android.bluetooth.le.ScanRecord; |
| 32 | import android.bluetooth.le.ScanResult; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 33 | import android.os.Parcel; |
| 34 | import android.provider.OneTimeUseBuilder; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 35 | import android.text.TextUtils; |
Eugene Susla | 3c9aa17 | 2017-03-28 15:04:12 -0700 | [diff] [blame] | 36 | import android.util.Log; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 37 | |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 38 | import com.android.internal.util.BitUtils; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 39 | import com.android.internal.util.ObjectUtils; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 40 | import com.android.internal.util.Preconditions; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 41 | |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 42 | import java.nio.ByteOrder; |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 43 | import java.util.Arrays; |
| 44 | import java.util.Objects; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 45 | import java.util.regex.Pattern; |
| 46 | |
| 47 | /** |
| 48 | * A filter for Bluetooth LE devices |
| 49 | * |
| 50 | * @see ScanFilter |
| 51 | */ |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 52 | public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> { |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 53 | |
Eugene Susla | 3c9aa17 | 2017-03-28 15:04:12 -0700 | [diff] [blame] | 54 | private static final boolean DEBUG = false; |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 55 | private static final String LOG_TAG = "BluetoothLeDeviceFilter"; |
Eugene Susla | 3c9aa17 | 2017-03-28 15:04:12 -0700 | [diff] [blame] | 56 | |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 57 | private static final int RENAME_PREFIX_LENGTH_LIMIT = 10; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 58 | |
| 59 | private final Pattern mNamePattern; |
| 60 | private final ScanFilter mScanFilter; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 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; |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 66 | private final int mRenameBytesLength; |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 67 | private final int mRenameNameFrom; |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 68 | private final int mRenameNameLength; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 69 | private final boolean mRenameBytesReverseOrder; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 70 | |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 71 | private BluetoothLeDeviceFilter(Pattern namePattern, ScanFilter scanFilter, |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 72 | byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix, |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 73 | String renameSuffix, int renameBytesFrom, int renameBytesLength, |
| 74 | int renameNameFrom, int renameNameLength, boolean renameBytesReverseOrder) { |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 75 | mNamePattern = namePattern; |
| 76 | mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY); |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 77 | mRawDataFilter = rawDataFilter; |
| 78 | mRawDataFilterMask = rawDataFilterMask; |
| 79 | mRenamePrefix = renamePrefix; |
| 80 | mRenameSuffix = renameSuffix; |
| 81 | mRenameBytesFrom = renameBytesFrom; |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 82 | mRenameBytesLength = renameBytesLength; |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 83 | mRenameNameFrom = renameNameFrom; |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 84 | mRenameNameLength = renameNameLength; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 85 | mRenameBytesReverseOrder = renameBytesReverseOrder; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 86 | } |
| 87 | |
| 88 | /** @hide */ |
| 89 | @Nullable |
| 90 | public Pattern getNamePattern() { |
| 91 | return mNamePattern; |
| 92 | } |
| 93 | |
| 94 | /** @hide */ |
| 95 | @NonNull |
Mathew Inwood | 70e89d5 | 2018-08-09 15:27:52 +0100 | [diff] [blame] | 96 | @UnsupportedAppUsage |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 97 | public ScanFilter getScanFilter() { |
| 98 | return mScanFilter; |
| 99 | } |
| 100 | |
| 101 | /** @hide */ |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 102 | @Nullable |
| 103 | public byte[] getRawDataFilter() { |
| 104 | return mRawDataFilter; |
| 105 | } |
| 106 | |
| 107 | /** @hide */ |
| 108 | @Nullable |
| 109 | public byte[] getRawDataFilterMask() { |
| 110 | return mRawDataFilterMask; |
| 111 | } |
| 112 | |
| 113 | /** @hide */ |
| 114 | @Nullable |
| 115 | public String getRenamePrefix() { |
| 116 | return mRenamePrefix; |
| 117 | } |
| 118 | |
| 119 | /** @hide */ |
| 120 | @Nullable |
| 121 | public String getRenameSuffix() { |
| 122 | return mRenameSuffix; |
| 123 | } |
| 124 | |
| 125 | /** @hide */ |
| 126 | public int getRenameBytesFrom() { |
| 127 | return mRenameBytesFrom; |
| 128 | } |
| 129 | |
| 130 | /** @hide */ |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 131 | public int getRenameBytesLength() { |
| 132 | return mRenameBytesLength; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 133 | } |
| 134 | |
| 135 | /** @hide */ |
| 136 | public boolean isRenameBytesReverseOrder() { |
| 137 | return mRenameBytesReverseOrder; |
| 138 | } |
| 139 | |
| 140 | /** @hide */ |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 141 | @Override |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 142 | @Nullable |
| 143 | public String getDeviceDisplayName(ScanResult sr) { |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 144 | if (mRenameBytesFrom < 0 && mRenameNameFrom < 0) { |
| 145 | return getDeviceDisplayNameInternal(sr.getDevice()); |
| 146 | } |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 147 | final StringBuilder sb = new StringBuilder(TextUtils.emptyIfNull(mRenamePrefix)); |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 148 | if (mRenameBytesFrom >= 0) { |
| 149 | final byte[] bytes = sr.getScanRecord().getBytes(); |
| 150 | int startInclusive = mRenameBytesFrom; |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 151 | int endInclusive = mRenameBytesFrom + mRenameBytesLength -1; |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 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()) |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 160 | .substring(mRenameNameFrom, mRenameNameFrom + mRenameNameLength)); |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 161 | } |
| 162 | return sb.append(TextUtils.emptyIfNull(mRenameSuffix)).toString(); |
| 163 | } |
| 164 | |
| 165 | /** @hide */ |
| 166 | @Override |
| 167 | public boolean matches(ScanResult device) { |
Eugene Susla | 3c9aa17 | 2017-03-28 15:04:12 -0700 | [diff] [blame] | 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; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 175 | } |
| 176 | |
| 177 | private boolean matches(BluetoothDevice device) { |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 178 | return BluetoothDeviceFilterUtils.matches(getScanFilter(), device) |
| 179 | && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device); |
| 180 | } |
| 181 | |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 182 | /** @hide */ |
| 183 | @Override |
| 184 | public int getMediumType() { |
| 185 | return DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE; |
| 186 | } |
| 187 | |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 188 | @Override |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 189 | public boolean equals(Object o) { |
| 190 | if (this == o) return true; |
| 191 | if (o == null || getClass() != o.getClass()) return false; |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 192 | BluetoothLeDeviceFilter that = (BluetoothLeDeviceFilter) o; |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 193 | return mRenameBytesFrom == that.mRenameBytesFrom && |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 194 | mRenameBytesLength == that.mRenameBytesLength && |
| 195 | mRenameNameFrom == that.mRenameNameFrom && |
| 196 | mRenameNameLength == that.mRenameNameLength && |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 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 |
| 207 | public int hashCode() { |
| 208 | return Objects.hash(mNamePattern, mScanFilter, mRawDataFilter, mRawDataFilterMask, |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 209 | mRenamePrefix, mRenameSuffix, mRenameBytesFrom, mRenameBytesLength, |
| 210 | mRenameNameFrom, mRenameNameLength, mRenameBytesReverseOrder); |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 211 | } |
| 212 | |
| 213 | @Override |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 214 | public void writeToParcel(Parcel dest, int flags) { |
| 215 | dest.writeString(patternToString(getNamePattern())); |
| 216 | dest.writeParcelable(mScanFilter, flags); |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 217 | dest.writeByteArray(mRawDataFilter); |
| 218 | dest.writeByteArray(mRawDataFilterMask); |
| 219 | dest.writeString(mRenamePrefix); |
| 220 | dest.writeString(mRenameSuffix); |
| 221 | dest.writeInt(mRenameBytesFrom); |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 222 | dest.writeInt(mRenameBytesLength); |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 223 | dest.writeInt(mRenameNameFrom); |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 224 | dest.writeInt(mRenameNameLength); |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 225 | dest.writeBoolean(mRenameBytesReverseOrder); |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 226 | } |
| 227 | |
| 228 | @Override |
| 229 | public int describeContents() { |
| 230 | return 0; |
| 231 | } |
| 232 | |
Eugene Susla | a7717e3 | 2017-04-17 19:13:31 -0700 | [diff] [blame] | 233 | @Override |
| 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 + |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 243 | ", mRenameBytesLength=" + mRenameBytesLength + |
Eugene Susla | a7717e3 | 2017-04-17 19:13:31 -0700 | [diff] [blame] | 244 | ", mRenameNameFrom=" + mRenameNameFrom + |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 245 | ", mRenameNameLength=" + mRenameNameLength + |
Eugene Susla | a7717e3 | 2017-04-17 19:13:31 -0700 | [diff] [blame] | 246 | ", mRenameBytesReverseOrder=" + mRenameBytesReverseOrder + |
| 247 | '}'; |
| 248 | } |
| 249 | |
Jeff Sharkey | 9e8f83d | 2019-02-28 12:06:45 -0700 | [diff] [blame] | 250 | public static final @android.annotation.NonNull Creator<BluetoothLeDeviceFilter> CREATOR |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 251 | = new Creator<BluetoothLeDeviceFilter>() { |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 252 | @Override |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 253 | public BluetoothLeDeviceFilter createFromParcel(Parcel in) { |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 254 | Builder builder = new Builder() |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 255 | .setNamePattern(patternFromString(in.readString())) |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 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(); |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 266 | int nameFrom = in.readInt(); |
| 267 | int nameTo = in.readInt(); |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 268 | boolean bytesReverseOrder = in.readBoolean(); |
| 269 | if (renamePrefix != null) { |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 270 | if (bytesFrom >= 0) { |
| 271 | builder.setRenameFromBytes(renamePrefix, suffix, bytesFrom, bytesTo, |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 272 | bytesReverseOrder ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 273 | } else { |
| 274 | builder.setRenameFromName(renamePrefix, suffix, nameFrom, nameTo); |
| 275 | } |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 276 | } |
| 277 | return builder.build(); |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 278 | } |
| 279 | |
| 280 | @Override |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 281 | public BluetoothLeDeviceFilter[] newArray(int size) { |
| 282 | return new BluetoothLeDeviceFilter[size]; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 283 | } |
| 284 | }; |
| 285 | |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 286 | public static int getRenamePrefixLengthLimit() { |
| 287 | return RENAME_PREFIX_LENGTH_LIMIT; |
| 288 | } |
| 289 | |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 290 | /** |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 291 | * Builder for {@link BluetoothLeDeviceFilter} |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 292 | */ |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 293 | public static final class Builder extends OneTimeUseBuilder<BluetoothLeDeviceFilter> { |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 294 | private ScanFilter mScanFilter; |
| 295 | private Pattern mNamePattern; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 296 | private byte[] mRawDataFilter; |
| 297 | private byte[] mRawDataFilterMask; |
| 298 | private String mRenamePrefix; |
| 299 | private String mRenameSuffix; |
| 300 | private int mRenameBytesFrom = -1; |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 301 | private int mRenameBytesLength; |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 302 | private int mRenameNameFrom = -1; |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 303 | private int mRenameNameLength; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 304 | private boolean mRenameBytesReverseOrder = false; |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 305 | |
| 306 | /** |
| 307 | * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the |
| 308 | * given regular expression will be shown |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 309 | * @return self for chaining |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 310 | */ |
| 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 | * |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 320 | * @return self for chaining |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 321 | * @see ScanFilter for specific details on its various fields |
| 322 | */ |
| 323 | @NonNull |
| 324 | public Builder setScanFilter(@Nullable ScanFilter scanFilter) { |
| 325 | checkNotUsed(); |
| 326 | mScanFilter = scanFilter; |
| 327 | return this; |
| 328 | } |
| 329 | |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 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 |
| 338 | public Builder setRawDataFilter(@NonNull byte[] rawDataFilter, |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 339 | @Nullable byte[] rawDataFilterMask) { |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 340 | checkNotUsed(); |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 341 | Preconditions.checkNotNull(rawDataFilter); |
| 342 | checkArgument(rawDataFilterMask == null || |
| 343 | rawDataFilter.length == rawDataFilterMask.length, |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 344 | "Mask and filter should be the same length"); |
Eugene Susla | a38fbf6 | 2017-03-14 10:26:10 -0700 | [diff] [blame] | 345 | mRawDataFilter = rawDataFilter; |
| 346 | mRawDataFilterMask = rawDataFilterMask; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 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) |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 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) |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 366 | * @return self for chaining |
| 367 | */ |
| 368 | @NonNull |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 369 | public Builder setRenameFromBytes(@NonNull String prefix, @NonNull String suffix, |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 370 | int bytesFrom, int bytesLength, ByteOrder byteOrder) { |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 371 | checkRenameNotSet(); |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 372 | checkRangeNotEmpty(bytesLength); |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 373 | mRenameBytesFrom = bytesFrom; |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 374 | mRenameBytesLength = bytesLength; |
| 375 | mRenameBytesReverseOrder = byteOrder == ByteOrder.LITTLE_ENDIAN; |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 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) |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 391 | * @param nameLength the number of characters to be displayed from the given index |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 392 | * @return self for chaining |
| 393 | */ |
| 394 | @NonNull |
| 395 | public Builder setRenameFromName(@NonNull String prefix, @NonNull String suffix, |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 396 | int nameFrom, int nameLength) { |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 397 | checkRenameNotSet(); |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 398 | checkRangeNotEmpty(nameLength); |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 399 | mRenameNameFrom = nameFrom; |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 400 | mRenameNameLength = nameLength; |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 401 | mRenameBytesReverseOrder = false; |
| 402 | return setRename(prefix, suffix); |
| 403 | } |
| 404 | |
| 405 | private void checkRenameNotSet() { |
| 406 | checkState(mRenamePrefix == null, "Renaming rule can only be set once"); |
| 407 | } |
| 408 | |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 409 | private void checkRangeNotEmpty(int length) { |
| 410 | checkArgument(length > 0, "Range must be non-empty"); |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 411 | } |
| 412 | |
| 413 | @NonNull |
| 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; |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 420 | return this; |
| 421 | } |
| 422 | |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 423 | /** @inheritDoc */ |
| 424 | @Override |
| 425 | @NonNull |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 426 | public BluetoothLeDeviceFilter build() { |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 427 | markUsed(); |
Eugene Susla | 722463f | 2017-04-19 15:22:39 -0700 | [diff] [blame] | 428 | return new BluetoothLeDeviceFilter(mNamePattern, mScanFilter, |
Eugene Susla | 36e866b | 2017-02-23 18:24:39 -0800 | [diff] [blame] | 429 | mRawDataFilter, mRawDataFilterMask, |
| 430 | mRenamePrefix, mRenameSuffix, |
Eugene Susla | 20bd9fc | 2017-04-27 14:34:59 -0700 | [diff] [blame] | 431 | mRenameBytesFrom, mRenameBytesLength, |
| 432 | mRenameNameFrom, mRenameNameLength, |
Eugene Susla | 75fb821 | 2017-04-05 10:36:24 -0700 | [diff] [blame] | 433 | mRenameBytesReverseOrder); |
Eugene Susla | 6ed45d8 | 2017-01-22 13:52:51 -0800 | [diff] [blame] | 434 | } |
| 435 | } |
| 436 | } |