• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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