1 /* 2 * Copyright 2021 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 com.android.server.nearby.common.bluetooth.fastpair; 18 19 import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH; 20 import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.decrypt; 21 import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.encrypt; 22 import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; 23 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.BLUETOOTH_ADDRESS_LENGTH; 24 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC; 25 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.DEVICE_ACTION; 26 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.ADDITIONAL_DATA_TYPE_INDEX; 27 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_ADDITIONAL_DATA_INDEX; 28 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_ADDITIONAL_DATA_LENGTH_INDEX; 29 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_CODE_INDEX; 30 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_GROUP_INDEX; 31 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.FLAGS_INDEX; 32 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.SEEKER_PUBLIC_ADDRESS_INDEX; 33 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.TYPE_ACTION_OVER_BLE; 34 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.TYPE_INDEX; 35 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.TYPE_KEY_BASED_PAIRING_REQUEST; 36 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.VERIFICATION_DATA_INDEX; 37 import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.VERIFICATION_DATA_LENGTH; 38 import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection.logRetrySuccessEvent; 39 import static com.android.server.nearby.common.bluetooth.fastpair.GattConnectionManager.isNoRetryError; 40 41 import static com.google.common.base.Verify.verifyNotNull; 42 import static com.google.common.io.BaseEncoding.base16; 43 import static com.google.common.primitives.Bytes.concat; 44 45 import static java.util.concurrent.TimeUnit.SECONDS; 46 47 import android.os.SystemClock; 48 import android.util.Log; 49 50 import androidx.annotation.Nullable; 51 import androidx.annotation.VisibleForTesting; 52 import androidx.core.util.Consumer; 53 54 import com.android.server.nearby.common.bluetooth.BluetoothException; 55 import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException; 56 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService; 57 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType; 58 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic; 59 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag; 60 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag; 61 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request; 62 import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection.SharedSecret; 63 import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection; 64 import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver; 65 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException; 66 import com.android.server.nearby.intdefs.FastPairEventIntDefs.ErrorCode; 67 import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode; 68 69 import java.security.GeneralSecurityException; 70 import java.security.SecureRandom; 71 import java.util.Arrays; 72 import java.util.UUID; 73 import java.util.concurrent.ExecutionException; 74 import java.util.concurrent.TimeoutException; 75 76 /** 77 * Handles the handshake step of Fast Pair, the Provider's public address and the shared secret will 78 * be disclosed during this step. It is the first step of all key-based operations, e.g. key-based 79 * pairing and action over BLE. 80 * 81 * @see <a href="https://developers.google.com/nearby/fast-pair/spec#procedure"> 82 * Fastpair Spec Procedure</a> 83 */ 84 public class HandshakeHandler { 85 86 private static final String TAG = HandshakeHandler.class.getSimpleName(); 87 private final GattConnectionManager mGattConnectionManager; 88 private final String mProviderBleAddress; 89 private final Preferences mPreferences; 90 private final EventLoggerWrapper mEventLogger; 91 @Nullable 92 private final FastPairConnection.FastPairSignalChecker mFastPairSignalChecker; 93 94 /** 95 * Keeps the keys used during handshaking, generated by {@link #createKey(byte[])}. 96 */ 97 private static final class Keys { 98 99 private final byte[] mSharedSecret; 100 private final byte[] mPublicKey; 101 Keys(byte[] sharedSecret, byte[] publicKey)102 private Keys(byte[] sharedSecret, byte[] publicKey) { 103 this.mSharedSecret = sharedSecret; 104 this.mPublicKey = publicKey; 105 } 106 } 107 HandshakeHandler( GattConnectionManager gattConnectionManager, String bleAddress, Preferences preferences, EventLoggerWrapper eventLogger, @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker)108 public HandshakeHandler( 109 GattConnectionManager gattConnectionManager, 110 String bleAddress, 111 Preferences preferences, 112 EventLoggerWrapper eventLogger, 113 @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker) { 114 this.mGattConnectionManager = gattConnectionManager; 115 this.mProviderBleAddress = bleAddress; 116 this.mPreferences = preferences; 117 this.mEventLogger = eventLogger; 118 this.mFastPairSignalChecker = fastPairSignalChecker; 119 } 120 121 /** 122 * Performs a handshake to authenticate and get the remote device's public address. Returns the 123 * AES-128 key as the shared secret for this pairing session. 124 */ doHandshake(byte[] key, HandshakeMessage message)125 public SharedSecret doHandshake(byte[] key, HandshakeMessage message) 126 throws GeneralSecurityException, InterruptedException, ExecutionException, 127 TimeoutException, BluetoothException, PairingException { 128 Keys keys = createKey(key); 129 Log.i(TAG, 130 "Handshake " + maskBluetoothAddress(mProviderBleAddress) + ", flags " 131 + message.mFlags); 132 byte[] handshakeResponse = 133 processGattCommunication( 134 createPacket(keys, message), 135 SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); 136 String providerPublicAddress = decodeResponse(keys.mSharedSecret, handshakeResponse); 137 138 return SharedSecret.create(keys.mSharedSecret, providerPublicAddress); 139 } 140 141 /** 142 * Performs a handshake to authenticate and get the remote device's public address. Returns the 143 * AES-128 key as the shared secret for this pairing session. Will retry and also performs 144 * FastPair signal check if fails. 145 */ doHandshakeWithRetryAndSignalLostCheck( byte[] key, HandshakeMessage message, @Nullable Consumer<Integer> rescueFromError)146 public SharedSecret doHandshakeWithRetryAndSignalLostCheck( 147 byte[] key, HandshakeMessage message, @Nullable Consumer<Integer> rescueFromError) 148 throws GeneralSecurityException, InterruptedException, ExecutionException, 149 TimeoutException, BluetoothException, PairingException { 150 Keys keys = createKey(key); 151 Log.i(TAG, 152 "Handshake " + maskBluetoothAddress(mProviderBleAddress) + ", flags " 153 + message.mFlags); 154 int retryCount = 0; 155 byte[] handshakeResponse = null; 156 long startTime = SystemClock.elapsedRealtime(); 157 BluetoothException lastException = null; 158 do { 159 try { 160 mEventLogger.setCurrentEvent(EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); 161 handshakeResponse = 162 processGattCommunication( 163 createPacket(keys, message), 164 getTimeoutMs(SystemClock.elapsedRealtime() - startTime)); 165 mEventLogger.logCurrentEventSucceeded(); 166 if (lastException != null) { 167 logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_HANDSHAKE, lastException, 168 mEventLogger); 169 } 170 } catch (BluetoothException e) { 171 lastException = e; 172 long spentTime = SystemClock.elapsedRealtime() - startTime; 173 Log.w(TAG, "Secret handshake failed, address=" 174 + maskBluetoothAddress(mProviderBleAddress) 175 + ", spent time=" + spentTime + "ms, retryCount=" + retryCount); 176 mEventLogger.logCurrentEventFailed(e); 177 178 if (!mPreferences.getRetryGattConnectionAndSecretHandshake()) { 179 throw e; 180 } 181 182 if (spentTime > mPreferences.getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs()) { 183 Log.w(TAG, "Spent too long time for handshake, timeInMs=" + spentTime); 184 throw e; 185 } 186 if (isNoRetryError(mPreferences, e)) { 187 throw e; 188 } 189 190 if (mFastPairSignalChecker != null) { 191 FastPairDualConnection 192 .checkFastPairSignal(mFastPairSignalChecker, mProviderBleAddress, e); 193 } 194 retryCount++; 195 if (retryCount > mPreferences.getSecretHandshakeRetryAttempts() 196 || ((e instanceof BluetoothOperationTimeoutException) 197 && !mPreferences.getRetrySecretHandshakeTimeout())) { 198 throw new HandshakeException("Fail on handshake!", e); 199 } 200 if (rescueFromError != null) { 201 rescueFromError.accept( 202 (e instanceof BluetoothTimeoutException 203 || e instanceof BluetoothOperationTimeoutException) 204 ? ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_TIMEOUT 205 : ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_ERROR); 206 } 207 } 208 } while (mPreferences.getRetryGattConnectionAndSecretHandshake() 209 && handshakeResponse == null); 210 if (retryCount > 0) { 211 Log.i(TAG, "Secret handshake failed but restored by retry, retry count=" + retryCount); 212 } 213 String providerPublicAddress = 214 decodeResponse(keys.mSharedSecret, verifyNotNull(handshakeResponse)); 215 216 return SharedSecret.create(keys.mSharedSecret, providerPublicAddress); 217 } 218 219 @VisibleForTesting getTimeoutMs(long spentTime)220 long getTimeoutMs(long spentTime) { 221 if (!mPreferences.getRetryGattConnectionAndSecretHandshake()) { 222 return SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()); 223 } else { 224 return spentTime < mPreferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs() 225 ? mPreferences.getSecretHandshakeShortTimeoutMs() 226 : mPreferences.getSecretHandshakeLongTimeoutMs(); 227 } 228 } 229 230 /** 231 * If the given key is an ecc-256 public key (currently, we are using secp256r1), the shared 232 * secret is generated by ECDH; if the input key is AES-128 key (should be the account key), 233 * then it is the shared secret. 234 */ createKey(byte[] key)235 private Keys createKey(byte[] key) throws GeneralSecurityException { 236 if (key.length == EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH) { 237 EllipticCurveDiffieHellmanExchange exchange = EllipticCurveDiffieHellmanExchange 238 .create(); 239 byte[] publicKey = exchange.getPublicKey(); 240 if (publicKey != null) { 241 Log.i(TAG, "Handshake " + maskBluetoothAddress(mProviderBleAddress) 242 + ", generates key by ECDH."); 243 } else { 244 throw new GeneralSecurityException("Failed to do ECDH."); 245 } 246 return new Keys(exchange.generateSecret(key), publicKey); 247 } else if (key.length == AesEcbSingleBlockEncryption.KEY_LENGTH) { 248 Log.i(TAG, "Handshake " + maskBluetoothAddress(mProviderBleAddress) 249 + ", using the given secret."); 250 return new Keys(key, new byte[0]); 251 } else { 252 throw new GeneralSecurityException("Key length is not correct: " + key.length); 253 } 254 } 255 createPacket(Keys keys, HandshakeMessage message)256 private static byte[] createPacket(Keys keys, HandshakeMessage message) 257 throws GeneralSecurityException { 258 byte[] encryptedMessage = encrypt(keys.mSharedSecret, message.getBytes()); 259 return concat(encryptedMessage, keys.mPublicKey); 260 } 261 processGattCommunication(byte[] packet, long gattOperationTimeoutMS)262 private byte[] processGattCommunication(byte[] packet, long gattOperationTimeoutMS) 263 throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { 264 BluetoothGattConnection gattConnection = mGattConnectionManager.getConnection(); 265 gattConnection.setOperationTimeout(gattOperationTimeoutMS); 266 UUID characteristicUuid = KeyBasedPairingCharacteristic.getId(gattConnection); 267 ChangeObserver changeObserver = 268 gattConnection.enableNotification(FastPairService.ID, characteristicUuid); 269 270 Log.i(TAG, 271 "Writing handshake packet to address=" + maskBluetoothAddress(mProviderBleAddress)); 272 gattConnection.writeCharacteristic(FastPairService.ID, characteristicUuid, packet); 273 Log.i(TAG, "Waiting handshake packet from address=" + maskBluetoothAddress( 274 mProviderBleAddress)); 275 return changeObserver.waitForUpdate(gattOperationTimeoutMS); 276 } 277 decodeResponse(byte[] sharedSecret, byte[] response)278 private String decodeResponse(byte[] sharedSecret, byte[] response) 279 throws PairingException, GeneralSecurityException { 280 if (response.length != AES_BLOCK_LENGTH) { 281 throw new PairingException( 282 "Handshake failed because of incorrect response: " + base16().encode(response)); 283 } 284 // 1 byte type, 6 bytes public address, remainder random salt. 285 byte[] decryptedResponse = decrypt(sharedSecret, response); 286 if (decryptedResponse[0] != KeyBasedPairingCharacteristic.Response.TYPE) { 287 throw new PairingException( 288 "Handshake response type incorrect: " + decryptedResponse[0]); 289 } 290 String address = BluetoothAddress.encode(Arrays.copyOfRange(decryptedResponse, 1, 7)); 291 Log.i(TAG, "Handshake success with public " + maskBluetoothAddress(address) + ", ble " 292 + maskBluetoothAddress(mProviderBleAddress)); 293 return address; 294 } 295 296 /** 297 * The base class for handshake message that contains the common data: message type, flags and 298 * verification data. 299 */ 300 abstract static class HandshakeMessage { 301 302 final byte mType; 303 final byte mFlags; 304 private final byte[] mVerificationData; 305 HandshakeMessage(Builder<?> builder)306 HandshakeMessage(Builder<?> builder) { 307 this.mType = builder.mType; 308 this.mVerificationData = builder.mVerificationData; 309 this.mFlags = builder.mFlags; 310 } 311 312 abstract static class Builder<T extends Builder<T>> { 313 314 byte mType; 315 byte mFlags; 316 private byte[] mVerificationData; 317 getThis()318 abstract T getThis(); 319 setVerificationData(byte[] verificationData)320 T setVerificationData(byte[] verificationData) { 321 if (verificationData.length != BLUETOOTH_ADDRESS_LENGTH) { 322 throw new IllegalArgumentException( 323 "Incorrect verification data length: " + verificationData.length + "."); 324 } 325 this.mVerificationData = verificationData; 326 return getThis(); 327 } 328 } 329 330 /** 331 * Constructs the base handshake message according to the format of Fast Pair spec. 332 */ constructBaseBytes()333 byte[] constructBaseBytes() { 334 byte[] rawMessage = new byte[Request.SIZE]; 335 new SecureRandom().nextBytes(rawMessage); 336 rawMessage[TYPE_INDEX] = mType; 337 rawMessage[FLAGS_INDEX] = mFlags; 338 339 System.arraycopy( 340 mVerificationData, 341 /* srcPos= */ 0, 342 rawMessage, 343 VERIFICATION_DATA_INDEX, 344 VERIFICATION_DATA_LENGTH); 345 return rawMessage; 346 } 347 348 /** 349 * Returns the raw handshake message. 350 */ getBytes()351 abstract byte[] getBytes(); 352 } 353 354 /** 355 * Extends {@link HandshakeMessage} and contains the required data for key-based pairing 356 * request. 357 */ 358 public static class KeyBasedPairingRequest extends HandshakeMessage { 359 360 @Nullable 361 private final byte[] mSeekerPublicAddress; 362 KeyBasedPairingRequest(Builder builder)363 private KeyBasedPairingRequest(Builder builder) { 364 super(builder); 365 this.mSeekerPublicAddress = builder.mSeekerPublicAddress; 366 } 367 368 @Override getBytes()369 byte[] getBytes() { 370 byte[] rawMessage = constructBaseBytes(); 371 if (mSeekerPublicAddress != null) { 372 System.arraycopy( 373 mSeekerPublicAddress, 374 /* srcPos= */ 0, 375 rawMessage, 376 SEEKER_PUBLIC_ADDRESS_INDEX, 377 BLUETOOTH_ADDRESS_LENGTH); 378 } 379 Log.i(TAG, 380 "Handshake Message: type (" + rawMessage[TYPE_INDEX] + "), flag (" 381 + rawMessage[FLAGS_INDEX] + ")."); 382 return rawMessage; 383 } 384 385 /** 386 * Builder class for key-based pairing request. 387 */ 388 public static class Builder extends HandshakeMessage.Builder<Builder> { 389 390 @Nullable 391 private byte[] mSeekerPublicAddress; 392 393 /** 394 * Adds flags without changing other flags. 395 */ addFlag(@eyBasedPairingRequestFlag int flag)396 public Builder addFlag(@KeyBasedPairingRequestFlag int flag) { 397 this.mFlags |= (byte) flag; 398 return this; 399 } 400 401 /** 402 * Set seeker's public address. 403 */ setSeekerPublicAddress(byte[] seekerPublicAddress)404 public Builder setSeekerPublicAddress(byte[] seekerPublicAddress) { 405 this.mSeekerPublicAddress = seekerPublicAddress; 406 return this; 407 } 408 409 /** 410 * Buulds KeyBasedPairigRequest. 411 */ build()412 public KeyBasedPairingRequest build() { 413 mType = TYPE_KEY_BASED_PAIRING_REQUEST; 414 return new KeyBasedPairingRequest(this); 415 } 416 417 @Override getThis()418 Builder getThis() { 419 return this; 420 } 421 } 422 } 423 424 /** 425 * Extends {@link HandshakeMessage} and contains the required data for action over BLE request. 426 */ 427 public static class ActionOverBle extends HandshakeMessage { 428 429 private final byte mEventGroup; 430 private final byte mEventCode; 431 @Nullable 432 private final byte[] mEventData; 433 private final byte mAdditionalDataType; 434 ActionOverBle(Builder builder)435 private ActionOverBle(Builder builder) { 436 super(builder); 437 this.mEventGroup = builder.mEventGroup; 438 this.mEventCode = builder.mEventCode; 439 this.mEventData = builder.mEventData; 440 this.mAdditionalDataType = builder.mAdditionalDataType; 441 } 442 443 @Override getBytes()444 byte[] getBytes() { 445 byte[] rawMessage = constructBaseBytes(); 446 StringBuilder stringBuilder = 447 new StringBuilder( 448 String.format( 449 "type (%02X), flag (%02X)", rawMessage[TYPE_INDEX], 450 rawMessage[FLAGS_INDEX])); 451 if ((mFlags & (byte) DEVICE_ACTION) != 0) { 452 rawMessage[EVENT_GROUP_INDEX] = mEventGroup; 453 rawMessage[EVENT_CODE_INDEX] = mEventCode; 454 455 if (mEventData != null) { 456 rawMessage[EVENT_ADDITIONAL_DATA_LENGTH_INDEX] = (byte) mEventData.length; 457 System.arraycopy( 458 mEventData, 459 /* srcPos= */ 0, 460 rawMessage, 461 EVENT_ADDITIONAL_DATA_INDEX, 462 mEventData.length); 463 } else { 464 rawMessage[EVENT_ADDITIONAL_DATA_LENGTH_INDEX] = (byte) 0; 465 } 466 stringBuilder.append( 467 String.format( 468 ", group(%02X), code(%02X), length(%02X)", 469 rawMessage[EVENT_GROUP_INDEX], 470 rawMessage[EVENT_CODE_INDEX], 471 rawMessage[EVENT_ADDITIONAL_DATA_LENGTH_INDEX])); 472 } 473 if ((mFlags & (byte) ADDITIONAL_DATA_CHARACTERISTIC) != 0) { 474 rawMessage[ADDITIONAL_DATA_TYPE_INDEX] = mAdditionalDataType; 475 stringBuilder.append( 476 String.format(", data id(%02X)", rawMessage[ADDITIONAL_DATA_TYPE_INDEX])); 477 } 478 Log.i(TAG, "Handshake Message: " + stringBuilder); 479 return rawMessage; 480 } 481 482 /** 483 * Builder class for action over BLE request. 484 */ 485 public static class Builder extends HandshakeMessage.Builder<Builder> { 486 487 private byte mEventGroup; 488 private byte mEventCode; 489 @Nullable 490 private byte[] mEventData; 491 private byte mAdditionalDataType; 492 493 // Adds a flag to this handshake message. This can be called repeatedly for adding 494 // different preference. 495 496 /** 497 * Adds flag without changing other flags. 498 */ addFlag(@ctionOverBleFlag int flag)499 public Builder addFlag(@ActionOverBleFlag int flag) { 500 this.mFlags |= (byte) flag; 501 return this; 502 } 503 504 /** 505 * Set event group and event code. 506 */ setEvent(int eventGroup, int eventCode)507 public Builder setEvent(int eventGroup, int eventCode) { 508 this.mFlags |= (byte) DEVICE_ACTION; 509 this.mEventGroup = (byte) (eventGroup & 0xFF); 510 this.mEventCode = (byte) (eventCode & 0xFF); 511 return this; 512 } 513 514 /** 515 * Set event additional data. 516 */ setEventAdditionalData(byte[] data)517 public Builder setEventAdditionalData(byte[] data) { 518 this.mEventData = data; 519 return this; 520 } 521 522 /** 523 * Set event additional data type. 524 */ setAdditionalDataType(@dditionalDataType int additionalDataType)525 public Builder setAdditionalDataType(@AdditionalDataType int additionalDataType) { 526 this.mFlags |= (byte) ADDITIONAL_DATA_CHARACTERISTIC; 527 this.mAdditionalDataType = (byte) additionalDataType; 528 return this; 529 } 530 531 @Override getThis()532 Builder getThis() { 533 return this; 534 } 535 build()536 ActionOverBle build() { 537 mType = TYPE_ACTION_OVER_BLE; 538 return new ActionOverBle(this); 539 } 540 } 541 } 542 543 /** 544 * Exception for handshake failure. 545 */ 546 public static class HandshakeException extends PairingException { 547 548 private final BluetoothException mOriginalException; 549 550 @VisibleForTesting HandshakeException(String format, BluetoothException e)551 HandshakeException(String format, BluetoothException e) { 552 super(format); 553 mOriginalException = e; 554 } 555 getOriginalException()556 public BluetoothException getOriginalException() { 557 return mOriginalException; 558 } 559 } 560 } 561