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 android.bluetooth.BluetoothDevice.BOND_BONDED; 20 import static android.bluetooth.BluetoothDevice.BOND_BONDING; 21 import static android.bluetooth.BluetoothDevice.BOND_NONE; 22 23 import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; 24 import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.get16BitUuid; 25 import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid; 26 import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toBytes; 27 import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toShorts; 28 29 import static com.google.common.base.Preconditions.checkNotNull; 30 import static com.google.common.base.Verify.verifyNotNull; 31 import static com.google.common.io.BaseEncoding.base16; 32 import static com.google.common.primitives.Bytes.concat; 33 34 import android.bluetooth.BluetoothDevice; 35 import android.bluetooth.BluetoothGattCharacteristic; 36 import android.content.BroadcastReceiver; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.IntentFilter; 40 import android.os.ParcelUuid; 41 import android.os.SystemClock; 42 import android.text.TextUtils; 43 import android.util.Log; 44 45 import androidx.annotation.GuardedBy; 46 import androidx.annotation.IntDef; 47 import androidx.annotation.Nullable; 48 import androidx.annotation.VisibleForTesting; 49 import androidx.annotation.WorkerThread; 50 51 import com.android.server.nearby.common.bluetooth.BluetoothException; 52 import com.android.server.nearby.common.bluetooth.BluetoothGattException; 53 import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException; 54 import com.android.server.nearby.common.bluetooth.fastpair.BluetoothAudioPairer.KeyBasedPairingInfo; 55 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService; 56 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic; 57 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType; 58 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.FirmwareVersionCharacteristic; 59 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag; 60 import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.NameCharacteristic; 61 import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService; 62 import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.ActionOverBle; 63 import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.HandshakeException; 64 import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.HandshakeMessage; 65 import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.KeyBasedPairingRequest; 66 import com.android.server.nearby.common.bluetooth.fastpair.Ltv.ParseException; 67 import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.ScopedTiming; 68 import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection; 69 import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver; 70 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter; 71 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException; 72 import com.android.server.nearby.common.locator.Locator; 73 import com.android.server.nearby.fastpair.FastPairController; 74 import com.android.server.nearby.intdefs.FastPairEventIntDefs.BrEdrHandoverErrorCode; 75 import com.android.server.nearby.intdefs.FastPairEventIntDefs.ConnectErrorCode; 76 import com.android.server.nearby.intdefs.FastPairEventIntDefs.CreateBondErrorCode; 77 import com.android.server.nearby.intdefs.FastPairEventIntDefs.ErrorCode; 78 import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode; 79 80 import com.google.common.base.Ascii; 81 import com.google.common.base.Preconditions; 82 import com.google.common.primitives.Shorts; 83 84 import java.lang.annotation.Retention; 85 import java.lang.annotation.RetentionPolicy; 86 import java.nio.ByteOrder; 87 import java.security.GeneralSecurityException; 88 import java.security.NoSuchAlgorithmException; 89 import java.util.ArrayList; 90 import java.util.Arrays; 91 import java.util.HashSet; 92 import java.util.List; 93 import java.util.Locale; 94 import java.util.Set; 95 import java.util.UUID; 96 import java.util.concurrent.CountDownLatch; 97 import java.util.concurrent.ExecutionException; 98 import java.util.concurrent.TimeUnit; 99 import java.util.concurrent.TimeoutException; 100 import java.util.concurrent.atomic.AtomicBoolean; 101 102 /** 103 * Supports Fast Pair pairing with certain Bluetooth headphones, Auto, etc. 104 * 105 * <p>Based on https://developers.google.com/nearby/fast-pair/spec, the pairing is constructed by 106 * both BLE and BREDR connections. Example state transitions for Fast Pair 2, ie a pairing key is 107 * included in the request (note: timeouts and retries are governed by flags, may change): 108 * 109 * <pre> 110 * {@code 111 * Connect GATT 112 * A) Success -> Handshake 113 * B) Failure (3s timeout) -> Retry 2x -> end 114 * 115 * Handshake 116 * A) Generate a shared secret with the headset (either using anti-spoofing key or account key) 117 * 1) Account key is used directly as the key 118 * 2) Anti-spoofing key is used by combining out private key with the headset's public and 119 * sending our public to the headset to combine with their private to generate a shared 120 * key. Sending our public key to headset takes ~3s. 121 * B) Write an encrypted packet to the headset containing their BLE address for verification 122 * that both sides have the same key (headset decodes this packet and checks it against their 123 * own address) (~250ms). 124 * C) Receive a response from the headset containing their public address (~250ms). 125 * 126 * Discovery (for devices < Oreo) 127 * A) Success -> Create Bond 128 * B) Failure (10s timeout) -> Sleep 1s, Retry 3x -> end 129 * 130 * Connect to device 131 * A) If already bonded 132 * 1) Attempt directly connecting to supported profiles (A2DP, etc) 133 * a) Success -> Write Account Key 134 * b) Failure (15s timeout, usually fails within a ~2s) -> Remove bond (~1s) -> Create bond 135 * B) If not already bonded 136 * 1) Create bond 137 * a) Success -> Connect profile 138 * b) Failure (15s timeout) -> Retry 2x -> end 139 * 2) Connect profile 140 * a) Success -> Write account key 141 * b) Failure -> Retry -> end 142 * 143 * Write account key 144 * A) Callback that pairing succeeded 145 * B) Disconnect GATT 146 * C) Reconnect GATT for secure connection 147 * D) Write account key (~3s) 148 * } 149 * </pre> 150 * 151 * The performance profiling result by {@link TimingLogger}: 152 * 153 * <pre> 154 * FastPairDualConnection [Exclusive time] / [Total time] ([Timestamp]) 155 * Connect GATT #1 3054ms (0) 156 * Handshake 32ms / 740ms (3054) 157 * Generate key via ECDH 10ms (3054) 158 * Add salt 1ms (3067) 159 * Encrypt request 3ms (3068) 160 * Write data to GATT 692ms (3097) 161 * Wait response from GATT 0ms (3789) 162 * Decrypt response 2ms (3789) 163 * Get BR/EDR handover information via SDP 1ms (3795) 164 * Pair device #1 6ms / 4887ms (3805) 165 * Create bond 3965ms / 4881ms (3809) 166 * Exchange passkey 587ms / 915ms (7124) 167 * Encrypt passkey 6ms (7694) 168 * Send passkey to remote 290ms (7700) 169 * Wait for remote passkey 0ms (7993) 170 * Decrypt passkey 18ms (7994) 171 * Confirm the pairing: true 14ms (8025) 172 * Close BondedReceiver 1ms (8688) 173 * Connect: A2DP 19ms / 370ms (8701) 174 * Wait connection 348ms / 349ms (8720) 175 * Close ConnectedReceiver 1ms (9068) 176 * Close profile: A2DP 2ms (9069) 177 * Write account key 2ms / 789ms (9163) 178 * Encrypt key 0ms (9164) 179 * Write key via GATT #1 777ms / 783ms (9164) 180 * Close GATT 6ms (9941) 181 * Start CloudSyncing 2ms (9947) 182 * Broadcast Validator 2ms (9949) 183 * FastPairDualConnection end, 9952ms 184 * </pre> 185 */ 186 // TODO(b/203441105): break down FastPairDualConnection into smaller classes. 187 public class FastPairDualConnection extends FastPairConnection { 188 189 private static final String TAG = FastPairDualConnection.class.getSimpleName(); 190 191 @VisibleForTesting 192 static final int GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST = 10000; 193 @VisibleForTesting 194 static final int GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED = 20000; 195 @VisibleForTesting 196 static final int GATT_ERROR_CODE_USER_RETRY = 30000; 197 @VisibleForTesting 198 static final int GATT_ERROR_CODE_PAIR_WITH_SAME_MODEL_ID_COUNT = 40000; 199 @VisibleForTesting 200 static final int GATT_ERROR_CODE_TIMEOUT = 1000; 201 202 @Nullable 203 private static String sInitialConnectionFirmwareVersion; 204 private static final byte[] REQUESTED_SERVICES_LTV = 205 new Ltv( 206 TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE, 207 toBytes( 208 ByteOrder.LITTLE_ENDIAN, 209 Constants.A2DP_SINK_SERVICE_UUID, 210 Constants.HANDS_FREE_SERVICE_UUID, 211 Constants.HEADSET_SERVICE_UUID)) 212 .getBytes(); 213 private static final byte[] TDS_CONTROL_POINT_REQUEST = 214 concat( 215 new byte[]{ 216 TransportDiscoveryService.ControlPointCharacteristic 217 .ACTIVATE_TRANSPORT_OP_CODE, 218 TransportDiscoveryService.BLUETOOTH_SIG_ORGANIZATION_ID 219 }, 220 REQUESTED_SERVICES_LTV); 221 222 private static boolean sTestMode = false; 223 enableTestMode()224 static void enableTestMode() { 225 sTestMode = true; 226 } 227 228 /** 229 * Operation Result Code. 230 */ 231 @Retention(RetentionPolicy.SOURCE) 232 @IntDef( 233 value = { 234 ResultCode.UNKNOWN, 235 ResultCode.SUCCESS, 236 ResultCode.OP_CODE_NOT_SUPPORTED, 237 ResultCode.INVALID_PARAMETER, 238 ResultCode.UNSUPPORTED_ORGANIZATION_ID, 239 ResultCode.OPERATION_FAILED, 240 }) 241 242 public @interface ResultCode { 243 244 int UNKNOWN = (byte) 0xFF; 245 int SUCCESS = (byte) 0x00; 246 int OP_CODE_NOT_SUPPORTED = (byte) 0x01; 247 int INVALID_PARAMETER = (byte) 0x02; 248 int UNSUPPORTED_ORGANIZATION_ID = (byte) 0x03; 249 int OPERATION_FAILED = (byte) 0x04; 250 } 251 252 fromTdsControlPointIndication(byte[] response)253 private static @ResultCode int fromTdsControlPointIndication(byte[] response) { 254 return response == null || response.length < 2 ? ResultCode.UNKNOWN : from(response[1]); 255 } 256 from(byte byteValue)257 private static @ResultCode int from(byte byteValue) { 258 switch (byteValue) { 259 case ResultCode.UNKNOWN: 260 case ResultCode.SUCCESS: 261 case ResultCode.OP_CODE_NOT_SUPPORTED: 262 case ResultCode.INVALID_PARAMETER: 263 case ResultCode.UNSUPPORTED_ORGANIZATION_ID: 264 case ResultCode.OPERATION_FAILED: 265 return byteValue; 266 default: 267 return ResultCode.UNKNOWN; 268 } 269 } 270 271 private static class BrEdrHandoverInformation { 272 273 private final byte[] mBluetoothAddress; 274 private final short[] mProfiles; 275 BrEdrHandoverInformation(byte[] bluetoothAddress, short[] profiles)276 private BrEdrHandoverInformation(byte[] bluetoothAddress, short[] profiles) { 277 this.mBluetoothAddress = bluetoothAddress; 278 279 // For now, since we only connect to one profile, prefer A2DP Sink over headset/HFP. 280 // TODO(b/37167120): Connect to more than one profile. 281 Set<Short> profileSet = new HashSet<>(Shorts.asList(profiles)); 282 if (profileSet.contains(Constants.A2DP_SINK_SERVICE_UUID)) { 283 profileSet.remove(Constants.HEADSET_SERVICE_UUID); 284 profileSet.remove(Constants.HANDS_FREE_SERVICE_UUID); 285 } 286 this.mProfiles = Shorts.toArray(profileSet); 287 } 288 289 @Override toString()290 public String toString() { 291 return "BrEdrHandoverInformation{" 292 + maskBluetoothAddress(BluetoothAddress.encode(mBluetoothAddress)) 293 + ", profiles=" 294 + (mProfiles.length > 0 ? Shorts.join(",", mProfiles) : "(none)") 295 + "}"; 296 } 297 } 298 299 private final Context mContext; 300 private final Preferences mPreferences; 301 private final EventLoggerWrapper mEventLogger; 302 private final BluetoothAdapter mBluetoothAdapter = 303 checkNotNull(BluetoothAdapter.getDefaultAdapter()); 304 private String mBleAddress; 305 306 private final TimingLogger mTimingLogger; 307 private GattConnectionManager mGattConnectionManager; 308 private boolean mProviderInitiatesBonding; 309 private @Nullable 310 byte[] mPairingSecret; 311 private @Nullable 312 byte[] mPairingKey; 313 @Nullable 314 private String mPublicAddress; 315 @VisibleForTesting 316 @Nullable 317 FastPairHistoryFinder mPairedHistoryFinder; 318 @Nullable 319 private String mProviderDeviceName = null; 320 private boolean mNeedUpdateProviderName = false; 321 @Nullable 322 DeviceNameReceiver mDeviceNameReceiver; 323 @Nullable 324 private HandshakeHandler mHandshakeHandlerForTest; 325 @Nullable 326 private Runnable mBeforeDirectlyConnectProfileFromCacheForTest; 327 FastPairDualConnection( Context context, String bleAddress, Preferences preferences, @Nullable EventLogger eventLogger)328 public FastPairDualConnection( 329 Context context, 330 String bleAddress, 331 Preferences preferences, 332 @Nullable EventLogger eventLogger) { 333 this(context, bleAddress, preferences, eventLogger, 334 new TimingLogger("FastPairDualConnection", preferences)); 335 } 336 337 @VisibleForTesting FastPairDualConnection( Context context, String bleAddress, Preferences preferences, @Nullable EventLogger eventLogger, TimingLogger timingLogger)338 FastPairDualConnection( 339 Context context, 340 String bleAddress, 341 Preferences preferences, 342 @Nullable EventLogger eventLogger, 343 TimingLogger timingLogger) { 344 this.mContext = context; 345 this.mPreferences = preferences; 346 this.mEventLogger = new EventLoggerWrapper(eventLogger); 347 this.mBleAddress = bleAddress; 348 this.mTimingLogger = timingLogger; 349 } 350 351 /** 352 * Unpairs with headphones. Synchronous: Blocks until unpaired. Throws on any error. 353 */ 354 @WorkerThread unpair(BluetoothDevice device)355 public void unpair(BluetoothDevice device) 356 throws ReflectionException, InterruptedException, ExecutionException, TimeoutException, 357 PairingException { 358 if (mPreferences.getExtraLoggingInformation() != null) { 359 mEventLogger 360 .bind(mContext, device.getAddress(), mPreferences.getExtraLoggingInformation()); 361 } 362 new BluetoothAudioPairer( 363 mContext, 364 device, 365 mPreferences, 366 mEventLogger, 367 /* keyBasedPairingInfo= */ null, 368 /* passkeyConfirmationHandler= */ null, 369 mTimingLogger) 370 .unpair(); 371 if (mEventLogger.isBound()) { 372 mEventLogger.unbind(mContext); 373 } 374 } 375 376 /** 377 * Sets the fast pair history for identifying the provider which has paired (without being 378 * forgotten) with the primary account on the device, i.e. the history is not limited on this 379 * phone, can be on other phones with the same account. If they have already paired, Fast Pair 380 * should not generate new account key and default personalized name for it after initial pair. 381 */ 382 @WorkerThread setFastPairHistory(List<FastPairHistoryItem> fastPairHistoryItem)383 public void setFastPairHistory(List<FastPairHistoryItem> fastPairHistoryItem) { 384 Log.i(TAG, "Paired history has been set."); 385 this.mPairedHistoryFinder = new FastPairHistoryFinder(fastPairHistoryItem); 386 } 387 388 /** 389 * Update the provider device name when we take provider default name and account based name 390 * into consideration. 391 */ setProviderDeviceName(String deviceName)392 public void setProviderDeviceName(String deviceName) { 393 Log.i(TAG, "Update provider device name = " + deviceName); 394 mProviderDeviceName = deviceName; 395 mNeedUpdateProviderName = true; 396 } 397 398 /** 399 * Gets the device name from the Provider (via GATT notify). 400 */ 401 @Nullable getProviderDeviceName()402 public String getProviderDeviceName() { 403 if (mDeviceNameReceiver == null) { 404 Log.i(TAG, "getProviderDeviceName failed, deviceNameReceiver == null."); 405 return null; 406 } 407 if (mPairingSecret == null) { 408 Log.i(TAG, "getProviderDeviceName failed, pairingSecret == null."); 409 return null; 410 } 411 String deviceName = mDeviceNameReceiver.getParsedResult(mPairingSecret); 412 Log.i(TAG, "getProviderDeviceName = " + deviceName); 413 414 return deviceName; 415 } 416 417 /** 418 * Get the existing account key of the provider, this API can be called after handshake. 419 * 420 * @return the existing account key if the provider has paired with the account before. 421 * Otherwise, return null, i.e. it is a real initial pairing. 422 */ 423 @WorkerThread 424 @Nullable getExistingAccountKey()425 public byte[] getExistingAccountKey() { 426 return mPairedHistoryFinder == null ? null : mPairedHistoryFinder.getExistingAccountKey(); 427 } 428 429 /** 430 * Pairs with headphones. Synchronous: Blocks until paired and connected. Throws on any error. 431 * 432 * @return the secret key for the user's account, if written. 433 */ 434 @WorkerThread 435 @Nullable pair()436 public SharedSecret pair() 437 throws BluetoothException, InterruptedException, ReflectionException, TimeoutException, 438 ExecutionException, PairingException { 439 try { 440 return pair(/*key=*/ null); 441 } catch (GeneralSecurityException e) { 442 throw new RuntimeException("Should never happen, no security key!", e); 443 } 444 } 445 446 /** 447 * Pairs with headphones. Synchronous: Blocks until paired and connected. Throws on any error. 448 * 449 * @param key can be in two different formats. If it is 16 bytes long, then it is an AES account 450 * key. Otherwise, it's a public key generated by {@link EllipticCurveDiffieHellmanExchange}. 451 * See go/fast-pair-2-spec for how each of these keys are used. 452 * @return the secret key for the user's account, if written 453 */ 454 @WorkerThread 455 @Nullable pair(@ullable byte[] key)456 public SharedSecret pair(@Nullable byte[] key) 457 throws BluetoothException, InterruptedException, ReflectionException, TimeoutException, 458 ExecutionException, PairingException, GeneralSecurityException { 459 mPairingKey = key; 460 if (key != null) { 461 Log.i(TAG, "Starting to pair " + maskBluetoothAddress(mBleAddress) + ": key[" 462 + key.length + "], " + mPreferences); 463 } else { 464 Log.i(TAG, "Pairing " + maskBluetoothAddress(mBleAddress) + ": " + mPreferences); 465 } 466 if (mPreferences.getExtraLoggingInformation() != null) { 467 this.mEventLogger.bind( 468 mContext, mBleAddress, mPreferences.getExtraLoggingInformation()); 469 } 470 // Provider never initiates if key is null (Fast Pair 1.0). 471 if (key != null && mPreferences.getProviderInitiatesBondingIfSupported()) { 472 // Provider can't initiate if we can't get our own public address, so check. 473 this.mEventLogger.setCurrentEvent(EventCode.GET_LOCAL_PUBLIC_ADDRESS); 474 if (BluetoothAddress.getPublicAddress(mContext) != null) { 475 this.mEventLogger.logCurrentEventSucceeded(); 476 mProviderInitiatesBonding = true; 477 } else { 478 this.mEventLogger 479 .logCurrentEventFailed(new IllegalStateException("null bluetooth_address")); 480 Log.e(TAG, 481 "Want provider to initiate bonding, but cannot access Bluetooth public " 482 + "address. Falling back to initiating bonding ourselves."); 483 } 484 } 485 486 // User might be pairing with a bonded device. In this case, we just connect profile 487 // directly and finish pairing. 488 if (directConnectProfileWithCachedAddress()) { 489 callbackOnPaired(); 490 mTimingLogger.dump(); 491 if (mEventLogger.isBound()) { 492 mEventLogger.unbind(mContext); 493 } 494 return null; 495 } 496 497 // Lazily initialize a new connection manager for each pairing request. 498 initGattConnectionManager(); 499 boolean isSecretHandshakeCompleted = true; 500 501 try { 502 if (key != null && key.length > 0) { 503 // GATT_CONNECTION_AND_SECRET_HANDSHAKE start. 504 mEventLogger.setCurrentEvent(EventCode.GATT_CONNECTION_AND_SECRET_HANDSHAKE); 505 isSecretHandshakeCompleted = false; 506 Exception lastException = null; 507 boolean lastExceptionFromHandshake = false; 508 long startTime = SystemClock.elapsedRealtime(); 509 // We communicate over this connection twice for Key-based Pairing: once before 510 // bonding begins, and once during (to transfer the passkey). Empirically, keeping 511 // it alive throughout is far more reliable than disconnecting and reconnecting for 512 // each step. The while loop is for retry of GATT connection and handshake only. 513 do { 514 boolean isHandshaking = false; 515 try (BluetoothGattConnection connection = 516 mGattConnectionManager 517 .getConnectionWithSignalLostCheck(mRescueFromError)) { 518 mEventLogger.setCurrentEvent(EventCode.SECRET_HANDSHAKE); 519 if (lastException != null && !lastExceptionFromHandshake) { 520 logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_GATT, lastException, 521 mEventLogger); 522 lastException = null; 523 } 524 try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, 525 "Handshake")) { 526 isHandshaking = true; 527 handshakeForKeyBasedPairing(key); 528 // After handshake, Fast Pair has the public address of the provider, so 529 // we can check if it has paired with the account. 530 if (mPublicAddress != null && mPairedHistoryFinder != null) { 531 if (mPairedHistoryFinder.isInPairedHistory(mPublicAddress)) { 532 Log.i(TAG, "The provider is found in paired history."); 533 } else { 534 Log.i(TAG, "The provider is not found in paired history."); 535 } 536 } 537 } 538 isHandshaking = false; 539 // SECRET_HANDSHAKE end. 540 mEventLogger.logCurrentEventSucceeded(); 541 isSecretHandshakeCompleted = true; 542 if (mPrepareCreateBondCallback != null) { 543 mPrepareCreateBondCallback.run(); 544 } 545 if (lastException != null && lastExceptionFromHandshake) { 546 logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_HANDSHAKE_RECONNECT, 547 lastException, mEventLogger); 548 } 549 logManualRetryCounts(/* success= */ true); 550 // GATT_CONNECTION_AND_SECRET_HANDSHAKE end. 551 mEventLogger.logCurrentEventSucceeded(); 552 return pair(mPreferences.getEnableBrEdrHandover()); 553 } catch (SignalLostException e) { 554 long spentTime = SystemClock.elapsedRealtime() - startTime; 555 if (spentTime > mPreferences.getAddressRotateRetryMaxSpentTimeMs()) { 556 Log.w(TAG, "Signal lost but already spend too much time " + spentTime 557 + "ms"); 558 throw e; 559 } 560 561 logCurrentEventFailedBySignalLost(e); 562 lastException = (Exception) e.getCause(); 563 lastExceptionFromHandshake = isHandshaking; 564 if (mRescueFromError != null && isHandshaking) { 565 mRescueFromError.accept(ErrorCode.SUCCESS_SECRET_HANDSHAKE_RECONNECT); 566 } 567 Log.i(TAG, "Signal lost, retry"); 568 // In case we meet some GATT error which is not recoverable and fail very 569 // quick. 570 SystemClock.sleep(mPreferences.getPairingRetryDelayMs()); 571 } catch (SignalRotatedException e) { 572 long spentTime = SystemClock.elapsedRealtime() - startTime; 573 if (spentTime > mPreferences.getAddressRotateRetryMaxSpentTimeMs()) { 574 Log.w(TAG, "Address rotated but already spend too much time " 575 + spentTime + "ms"); 576 throw e; 577 } 578 579 logCurrentEventFailedBySignalRotated(e); 580 setBleAddress(e.getNewAddress()); 581 lastException = (Exception) e.getCause(); 582 lastExceptionFromHandshake = isHandshaking; 583 if (mRescueFromError != null) { 584 mRescueFromError.accept(ErrorCode.SUCCESS_ADDRESS_ROTATE); 585 } 586 Log.i(TAG, "Address rotated, retry"); 587 } catch (HandshakeException e) { 588 long spentTime = SystemClock.elapsedRealtime() - startTime; 589 if (spentTime > mPreferences 590 .getSecretHandshakeRetryGattConnectionMaxSpentTimeMs()) { 591 Log.w(TAG, "Secret handshake failed but already spend too much time " 592 + spentTime + "ms"); 593 throw e.getOriginalException(); 594 } 595 if (mEventLogger.isCurrentEvent()) { 596 mEventLogger.logCurrentEventFailed(e.getOriginalException()); 597 } 598 initGattConnectionManager(); 599 lastException = e.getOriginalException(); 600 lastExceptionFromHandshake = true; 601 if (mRescueFromError != null) { 602 mRescueFromError.accept(ErrorCode.SUCCESS_SECRET_HANDSHAKE_RECONNECT); 603 } 604 Log.i(TAG, "Handshake failed, retry GATT connection"); 605 } 606 } while (mPreferences.getRetryGattConnectionAndSecretHandshake()); 607 } 608 if (mPrepareCreateBondCallback != null) { 609 mPrepareCreateBondCallback.run(); 610 } 611 return pair(mPreferences.getEnableBrEdrHandover()); 612 } catch (SignalLostException e) { 613 logCurrentEventFailedBySignalLost(e); 614 // GATT_CONNECTION_AND_SECRET_HANDSHAKE end. 615 if (!isSecretHandshakeCompleted) { 616 logManualRetryCounts(/* success= */ false); 617 logCurrentEventFailedBySignalLost(e); 618 } 619 throw e; 620 } catch (SignalRotatedException e) { 621 logCurrentEventFailedBySignalRotated(e); 622 // GATT_CONNECTION_AND_SECRET_HANDSHAKE end. 623 if (!isSecretHandshakeCompleted) { 624 logManualRetryCounts(/* success= */ false); 625 logCurrentEventFailedBySignalRotated(e); 626 } 627 throw e; 628 } catch (BluetoothException 629 | InterruptedException 630 | ReflectionException 631 | TimeoutException 632 | ExecutionException 633 | PairingException 634 | GeneralSecurityException e) { 635 if (mEventLogger.isCurrentEvent()) { 636 mEventLogger.logCurrentEventFailed(e); 637 } 638 // GATT_CONNECTION_AND_SECRET_HANDSHAKE end. 639 if (!isSecretHandshakeCompleted) { 640 logManualRetryCounts(/* success= */ false); 641 if (mEventLogger.isCurrentEvent()) { 642 mEventLogger.logCurrentEventFailed(e); 643 } 644 } 645 throw e; 646 } finally { 647 mTimingLogger.dump(); 648 if (mEventLogger.isBound()) { 649 mEventLogger.unbind(mContext); 650 } 651 } 652 } 653 directConnectProfileWithCachedAddress()654 private boolean directConnectProfileWithCachedAddress() throws ReflectionException { 655 if (TextUtils.isEmpty(mPreferences.getCachedDeviceAddress()) 656 || !mPreferences.getDirectConnectProfileIfModelIdInCache() 657 || mPreferences.getSkipConnectingProfiles()) { 658 return false; 659 } 660 Log.i(TAG, "Try to direct connect profile with cached address " 661 + maskBluetoothAddress(mPreferences.getCachedDeviceAddress())); 662 mEventLogger.setCurrentEvent(EventCode.DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS); 663 BluetoothDevice device = 664 mBluetoothAdapter.getRemoteDevice(mPreferences.getCachedDeviceAddress()).unwrap(); 665 AtomicBoolean interruptConnection = new AtomicBoolean(false); 666 BroadcastReceiver receiver = 667 new BroadcastReceiver() { 668 @Override 669 public void onReceive(Context context, Intent intent) { 670 if (intent == null 671 || !BluetoothDevice.ACTION_PAIRING_REQUEST 672 .equals(intent.getAction())) { 673 return; 674 } 675 BluetoothDevice pairingDevice = intent 676 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 677 if (pairingDevice == null || !device.getAddress() 678 .equals(pairingDevice.getAddress())) { 679 return; 680 } 681 abortBroadcast(); 682 // Should be the clear link key case, make it fail directly to go back to 683 // initial pairing process. 684 pairingDevice.setPairingConfirmation(/* confirm= */ false); 685 Log.w(TAG, "Get pairing request broadcast for device " 686 + maskBluetoothAddress(device.getAddress()) 687 + " while try to direct connect profile with cached address, reject" 688 + " and to go back to initial pairing process"); 689 interruptConnection.set(true); 690 } 691 }; 692 mContext.registerReceiver(receiver, 693 new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)); 694 try (ScopedTiming scopedTiming = 695 new ScopedTiming(mTimingLogger, 696 "Connect to profile with cached address directly")) { 697 if (mBeforeDirectlyConnectProfileFromCacheForTest != null) { 698 mBeforeDirectlyConnectProfileFromCacheForTest.run(); 699 } 700 attemptConnectProfiles( 701 new BluetoothAudioPairer( 702 mContext, 703 device, 704 mPreferences, 705 mEventLogger, 706 /* keyBasedPairingInfo= */ null, 707 /* passkeyConfirmationHandler= */ null, 708 mTimingLogger), 709 maskBluetoothAddress(device), 710 getSupportedProfiles(device), 711 /* numConnectionAttempts= */ 1, 712 /* enablePairingBehavior= */ false, 713 interruptConnection); 714 Log.i(TAG, 715 "Directly connected to " + maskBluetoothAddress(device) 716 + "with cached address."); 717 mEventLogger.logCurrentEventSucceeded(); 718 mEventLogger.setDevice(device); 719 logPairWithPossibleCachedAddress(device.getAddress()); 720 return true; 721 } catch (PairingException e) { 722 if (interruptConnection.get()) { 723 Log.w(TAG, "Fail to connected to " + maskBluetoothAddress(device) 724 + " with cached address due to link key is cleared.", e); 725 mEventLogger.logCurrentEventFailed( 726 new ConnectException(ConnectErrorCode.LINK_KEY_CLEARED, 727 "Link key is cleared")); 728 } else { 729 Log.w(TAG, "Fail to connected to " + maskBluetoothAddress(device) 730 + " with cached address.", e); 731 mEventLogger.logCurrentEventFailed(e); 732 } 733 return false; 734 } finally { 735 mContext.unregisterReceiver(receiver); 736 } 737 } 738 739 /** 740 * Logs for user retry, check go/fastpairquality21q3 for more details. 741 */ logManualRetryCounts(boolean success)742 private void logManualRetryCounts(boolean success) { 743 if (!mPreferences.getLogUserManualRetry()) { 744 return; 745 } 746 747 // We don't want to be the final event on analytics. 748 if (!mEventLogger.isCurrentEvent()) { 749 return; 750 } 751 752 mEventLogger.setCurrentEvent(EventCode.GATT_HANDSHAKE_MANUAL_RETRY_ATTEMPTS); 753 if (mPreferences.getPairFailureCounts() <= 0 && success) { 754 mEventLogger.logCurrentEventSucceeded(); 755 } else { 756 int errorCode = mPreferences.getPairFailureCounts(); 757 if (errorCode > 99) { 758 errorCode = 99; 759 } 760 errorCode += success ? 0 : 100; 761 // To not conflict with current error codes. 762 errorCode += GATT_ERROR_CODE_USER_RETRY; 763 mEventLogger.logCurrentEventFailed( 764 new BluetoothGattException("Error for manual retry", errorCode)); 765 } 766 } 767 logRetrySuccessEvent( @ventCode int eventCode, @Nullable Exception recoverFromException, EventLoggerWrapper eventLogger)768 static void logRetrySuccessEvent( 769 @EventCode int eventCode, 770 @Nullable Exception recoverFromException, 771 EventLoggerWrapper eventLogger) { 772 if (recoverFromException == null) { 773 return; 774 } 775 eventLogger.setCurrentEvent(eventCode); 776 eventLogger.logCurrentEventFailed(recoverFromException); 777 } 778 initGattConnectionManager()779 private void initGattConnectionManager() { 780 mGattConnectionManager = 781 new GattConnectionManager( 782 mContext, 783 mPreferences, 784 mEventLogger, 785 mBluetoothAdapter, 786 this::toggleBluetooth, 787 mBleAddress, 788 mTimingLogger, 789 mFastPairSignalChecker, 790 isPairingWithAntiSpoofingPublicKey()); 791 } 792 logCurrentEventFailedBySignalRotated(SignalRotatedException e)793 private void logCurrentEventFailedBySignalRotated(SignalRotatedException e) { 794 if (!mEventLogger.isCurrentEvent()) { 795 return; 796 } 797 798 Log.w(TAG, "BLE Address for pairing device might rotated!"); 799 mEventLogger.logCurrentEventFailed( 800 new BluetoothGattException( 801 "BLE Address for pairing device might rotated", 802 appendMoreErrorCode(GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED, 803 e.getCause()), 804 e)); 805 } 806 logCurrentEventFailedBySignalLost(SignalLostException e)807 private void logCurrentEventFailedBySignalLost(SignalLostException e) { 808 if (!mEventLogger.isCurrentEvent()) { 809 return; 810 } 811 812 Log.w(TAG, "BLE signal for pairing device might lost!"); 813 mEventLogger.logCurrentEventFailed( 814 new BluetoothGattException( 815 "BLE signal for pairing device might lost", 816 appendMoreErrorCode(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST, e.getCause()), 817 e)); 818 } 819 820 @VisibleForTesting appendMoreErrorCode(int masterErrorCode, @Nullable Throwable cause)821 static int appendMoreErrorCode(int masterErrorCode, @Nullable Throwable cause) { 822 if (cause instanceof BluetoothGattException) { 823 return masterErrorCode + ((BluetoothGattException) cause).getGattErrorCode(); 824 } else if (cause instanceof TimeoutException 825 || cause instanceof BluetoothTimeoutException 826 || cause instanceof BluetoothOperationTimeoutException) { 827 return masterErrorCode + GATT_ERROR_CODE_TIMEOUT; 828 } else { 829 return masterErrorCode; 830 } 831 } 832 setBleAddress(String newAddress)833 private void setBleAddress(String newAddress) { 834 if (TextUtils.isEmpty(newAddress) || Ascii.equalsIgnoreCase(newAddress, mBleAddress)) { 835 return; 836 } 837 838 mBleAddress = newAddress; 839 840 // Recreates a GattConnectionManager with the new address for establishing a new GATT 841 // connection later. 842 initGattConnectionManager(); 843 844 mEventLogger.setDevice(mBluetoothAdapter.getRemoteDevice(mBleAddress).unwrap()); 845 } 846 847 /** 848 * Gets the public address of the headset used in the connection. Before the handshake, this 849 * could be null. 850 */ 851 @Nullable getPublicAddress()852 public String getPublicAddress() { 853 return mPublicAddress; 854 } 855 856 /** 857 * Pairs with a Bluetooth device. In general, this process goes through the following steps: 858 * 859 * <ol> 860 * <li>Get BrEdr handover information if requested 861 * <li>Discover the device (on Android N and lower to work around a bug) 862 * <li>Connect to the device 863 * <ul> 864 * <li>Attempt a direct connection to a supported profile if we're already bonded 865 * <li>Create a new bond with the not bonded device and then connect to a supported 866 * profile 867 * </ul> 868 * <li>Write the account secret 869 * </ol> 870 * 871 * <p>Blocks until paired. May take 10+ seconds, so run on a background thread. 872 */ 873 @Nullable pair(boolean enableBrEdrHandover)874 private SharedSecret pair(boolean enableBrEdrHandover) 875 throws BluetoothException, InterruptedException, ReflectionException, TimeoutException, 876 ExecutionException, PairingException, GeneralSecurityException { 877 BrEdrHandoverInformation brEdrHandoverInformation = null; 878 if (enableBrEdrHandover) { 879 try (ScopedTiming scopedTiming = 880 new ScopedTiming(mTimingLogger, "Get BR/EDR handover information via GATT")) { 881 brEdrHandoverInformation = 882 getBrEdrHandoverInformation(mGattConnectionManager.getConnection()); 883 } catch (BluetoothException | TdsException e) { 884 Log.w(TAG, 885 "Couldn't get BR/EDR Handover info via TDS. Trying direct connect.", e); 886 mEventLogger.logCurrentEventFailed(e); 887 } 888 } 889 890 if (brEdrHandoverInformation == null) { 891 // Pair directly to the BLE address. Works if the BLE and Bluetooth Classic addresses 892 // are the same, or if we can do BLE cross-key transport. 893 brEdrHandoverInformation = 894 new BrEdrHandoverInformation( 895 BluetoothAddress 896 .decode(mPublicAddress != null ? mPublicAddress : mBleAddress), 897 attemptGetBluetoothClassicProfiles( 898 mBluetoothAdapter.getRemoteDevice(mBleAddress).unwrap(), 899 mPreferences.getNumSdpAttempts())); 900 } 901 902 BluetoothDevice device = 903 mBluetoothAdapter.getRemoteDevice(brEdrHandoverInformation.mBluetoothAddress) 904 .unwrap(); 905 callbackOnGetAddress(device.getAddress()); 906 mEventLogger.setDevice(device); 907 908 Log.i(TAG, "Pairing with " + brEdrHandoverInformation); 909 KeyBasedPairingInfo keyBasedPairingInfo = 910 mPairingSecret == null 911 ? null 912 : new KeyBasedPairingInfo( 913 mPairingSecret, mGattConnectionManager, mProviderInitiatesBonding); 914 915 BluetoothAudioPairer pairer = 916 new BluetoothAudioPairer( 917 mContext, 918 device, 919 mPreferences, 920 mEventLogger, 921 keyBasedPairingInfo, 922 mPasskeyConfirmationHandler, 923 mTimingLogger); 924 925 logPairWithPossibleCachedAddress(device.getAddress()); 926 logPairWithModelIdInCacheAndDiscoveryFailForCachedAddress(device); 927 928 // In the case where we are already bonded, we should first just try connecting to supported 929 // profiles. If successful, then this will be much faster than recreating the bond like we 930 // normally do and we can finish early. It is also more reliable than tearing down the bond 931 // and recreating it. 932 try { 933 if (!sTestMode) { 934 attemptDirectConnectionIfBonded(device, pairer); 935 } 936 callbackOnPaired(); 937 return maybeWriteAccountKey(device); 938 } catch (PairingException e) { 939 Log.i(TAG, "Failed to directly connect to supported profiles: " + e.getMessage()); 940 // Catches exception when we fail to connect support profile. And makes the flow to go 941 // through step to write account key when device is bonded. 942 if (mPreferences.getEnablePairFlowShowUiWithoutProfileConnection() 943 && device.getBondState() == BluetoothDevice.BOND_BONDED) { 944 if (mPreferences.getSkipConnectingProfiles() 945 && !mPreferences.getCheckBondStateWhenSkipConnectingProfiles()) { 946 Log.i(TAG, "For notCheckBondStateWhenSkipConnectingProfiles case should do " 947 + "re-bond"); 948 } else { 949 Log.i(TAG, "Fail to connect profile when device is bonded, still call back on" 950 + "pair callback to show ui"); 951 callbackOnPaired(); 952 return maybeWriteAccountKey(device); 953 } 954 } 955 } 956 957 if (mPreferences.getMoreEventLogForQuality()) { 958 switch (device.getBondState()) { 959 case BOND_BONDED: 960 mEventLogger.setCurrentEvent(EventCode.BEFORE_CREATE_BOND_BONDED); 961 break; 962 case BOND_BONDING: 963 mEventLogger.setCurrentEvent(EventCode.BEFORE_CREATE_BOND_BONDING); 964 break; 965 case BOND_NONE: 966 default: 967 mEventLogger.setCurrentEvent(EventCode.BEFORE_CREATE_BOND); 968 } 969 } 970 971 for (int i = 1; i <= mPreferences.getNumCreateBondAttempts(); i++) { 972 try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Pair device #" + i)) { 973 pairer.pair(); 974 if (mPreferences.getMoreEventLogForQuality()) { 975 // For EventCode.BEFORE_CREATE_BOND 976 mEventLogger.logCurrentEventSucceeded(); 977 } 978 break; 979 } catch (Exception e) { 980 mEventLogger.logCurrentEventFailed(e); 981 if (mPasskeyIsGotten) { 982 Log.w(TAG, 983 "createBond() failed because of " + e.getMessage() 984 + " after getting the passkey. Skip retry."); 985 if (mPreferences.getMoreEventLogForQuality()) { 986 // For EventCode.BEFORE_CREATE_BOND 987 mEventLogger.logCurrentEventFailed( 988 new CreateBondException( 989 CreateBondErrorCode.FAILED_BUT_ALREADY_RECEIVE_PASS_KEY, 990 0, 991 "Already get the passkey")); 992 } 993 break; 994 } 995 Log.e(TAG, 996 "removeBond() or createBond() failed, attempt " + i + " of " + mPreferences 997 .getNumCreateBondAttempts() + ". Bond state " 998 + device.getBondState(), e); 999 if (i < mPreferences.getNumCreateBondAttempts()) { 1000 toggleBluetooth(); 1001 1002 // We've seen 3 createBond() failures within 100ms (!). And then success again 1003 // later (even without turning on/off bluetooth). So create some minimum break 1004 // time. 1005 Log.i(TAG, "Sleeping 1 sec after createBond() failure."); 1006 SystemClock.sleep(1000); 1007 } else if (mPreferences.getMoreEventLogForQuality()) { 1008 // For EventCode.BEFORE_CREATE_BOND 1009 mEventLogger.logCurrentEventFailed(e); 1010 } 1011 } 1012 } 1013 boolean deviceCreateBondFailWithNullSecret = false; 1014 if (!pairer.isPaired()) { 1015 if (mPairingSecret != null) { 1016 // Bonding could fail for a few different reasons here. It could be an error, an 1017 // attacker may have tried to bond, or the device may not be up to spec. 1018 throw new PairingException("createBond() failed, exiting connection process."); 1019 } else if (mPreferences.getSkipConnectingProfiles()) { 1020 throw new PairingException( 1021 "createBond() failed and skipping connecting to a profile."); 1022 } else { 1023 // When bond creation has failed, connecting a profile will still work most of the 1024 // time for Fast Pair 1.0 devices (ie, pairing secret is null), so continue on with 1025 // the spec anyways and attempt to connect supported profiles. 1026 Log.w(TAG, "createBond() failed, will try connecting profiles anyway."); 1027 deviceCreateBondFailWithNullSecret = true; 1028 } 1029 } else if (mPreferences.getEnablePairFlowShowUiWithoutProfileConnection()) { 1030 Log.i(TAG, "new flow to call on paired callback for ui when pairing step is finished"); 1031 callbackOnPaired(); 1032 } 1033 1034 if (!mPreferences.getSkipConnectingProfiles()) { 1035 if (mPreferences.getWaitForUuidsAfterBonding() 1036 && brEdrHandoverInformation.mProfiles.length == 0) { 1037 short[] supportedProfiles = getCachedUuids(device); 1038 if (supportedProfiles.length == 0 1039 && mPreferences.getNumSdpAttemptsAfterBonded() > 0) { 1040 Log.i(TAG, "Found no supported profiles in UUID cache, manually trigger SDP."); 1041 attemptGetBluetoothClassicProfiles(device, 1042 mPreferences.getNumSdpAttemptsAfterBonded()); 1043 } 1044 brEdrHandoverInformation = 1045 new BrEdrHandoverInformation( 1046 brEdrHandoverInformation.mBluetoothAddress, supportedProfiles); 1047 } 1048 short[] profiles = brEdrHandoverInformation.mProfiles; 1049 if (profiles.length == 0) { 1050 profiles = Constants.getSupportedProfiles(); 1051 Log.w(TAG, 1052 "Attempting to connect constants profiles, " + Arrays.toString(profiles)); 1053 } else { 1054 Log.i(TAG, "Attempting to connect device profiles, " + Arrays.toString(profiles)); 1055 } 1056 1057 try { 1058 attemptConnectProfiles( 1059 pairer, 1060 maskBluetoothAddress(device), 1061 profiles, 1062 mPreferences.getNumConnectAttempts(), 1063 /* enablePairingBehavior= */ false); 1064 } catch (PairingException e) { 1065 // For new pair flow to show ui, we already show success ui when finishing the 1066 // createBond step. So we should catch the exception from connecting profile to 1067 // avoid showing fail ui for user. 1068 if (mPreferences.getEnablePairFlowShowUiWithoutProfileConnection() 1069 && !deviceCreateBondFailWithNullSecret) { 1070 Log.i(TAG, "Fail to connect profile when device is bonded"); 1071 } else { 1072 throw e; 1073 } 1074 } 1075 } 1076 if (!mPreferences.getEnablePairFlowShowUiWithoutProfileConnection()) { 1077 Log.i(TAG, "original flow to call on paired callback for ui"); 1078 callbackOnPaired(); 1079 } else if (deviceCreateBondFailWithNullSecret) { 1080 // This paired callback is called for device which create bond fail with null secret 1081 // such as FastPair 1.0 device when directly connecting to any supported profile. 1082 Log.i(TAG, "call on paired callback for ui for device with null secret without bonded " 1083 + "state"); 1084 callbackOnPaired(); 1085 } 1086 if (mPreferences.getEnableFirmwareVersionCharacteristic() 1087 && validateBluetoothGattCharacteristic( 1088 mGattConnectionManager.getConnection(), FirmwareVersionCharacteristic.ID)) { 1089 try { 1090 sInitialConnectionFirmwareVersion = readFirmwareVersion(); 1091 } catch (BluetoothException e) { 1092 Log.i(TAG, "Fast Pair: head phone does not support firmware read", e); 1093 } 1094 } 1095 1096 // Catch exception when writing account key or name fail to avoid showing pairing failure 1097 // notice for user. Because device is already paired successfully based on paring step. 1098 SharedSecret secret = null; 1099 try { 1100 secret = maybeWriteAccountKey(device); 1101 } catch (InterruptedException 1102 | ExecutionException 1103 | TimeoutException 1104 | NoSuchAlgorithmException 1105 | BluetoothException e) { 1106 Log.w(TAG, "Fast Pair: Got exception when writing account key or name to provider", e); 1107 } 1108 1109 return secret; 1110 } 1111 logPairWithPossibleCachedAddress(String brEdrAddressForBonding)1112 private void logPairWithPossibleCachedAddress(String brEdrAddressForBonding) { 1113 if (TextUtils.isEmpty(mPreferences.getPossibleCachedDeviceAddress()) 1114 || !mPreferences.getLogPairWithCachedModelId()) { 1115 return; 1116 } 1117 mEventLogger.setCurrentEvent(EventCode.PAIR_WITH_CACHED_MODEL_ID); 1118 if (Ascii.equalsIgnoreCase( 1119 mPreferences.getPossibleCachedDeviceAddress(), brEdrAddressForBonding)) { 1120 mEventLogger.logCurrentEventSucceeded(); 1121 Log.i(TAG, "Repair with possible cached device " 1122 + maskBluetoothAddress(mPreferences.getPossibleCachedDeviceAddress())); 1123 } else { 1124 mEventLogger.logCurrentEventFailed( 1125 new PairingException("Pairing with 2nd device with same model ID")); 1126 Log.i(TAG, "Pair with a new device " + maskBluetoothAddress(brEdrAddressForBonding) 1127 + " with model ID in cache " 1128 + maskBluetoothAddress(mPreferences.getPossibleCachedDeviceAddress())); 1129 } 1130 } 1131 1132 /** 1133 * Logs two type of events. First, why cachedAddress mechanism doesn't work if it's repair with 1134 * bonded device case. Second, if it's not the case, log how many devices with the same model Id 1135 * is already paired. 1136 */ logPairWithModelIdInCacheAndDiscoveryFailForCachedAddress(BluetoothDevice device)1137 private void logPairWithModelIdInCacheAndDiscoveryFailForCachedAddress(BluetoothDevice device) { 1138 if (!mPreferences.getLogPairWithCachedModelId()) { 1139 return; 1140 } 1141 1142 if (device.getBondState() == BOND_BONDED) { 1143 if (mPreferences.getSameModelIdPairedDeviceCount() <= 0) { 1144 Log.i(TAG, "Device is bonded but we don't have this model Id in cache."); 1145 } else if (TextUtils.isEmpty(mPreferences.getCachedDeviceAddress()) 1146 && mPreferences.getDirectConnectProfileIfModelIdInCache() 1147 && !mPreferences.getSkipConnectingProfiles()) { 1148 // Pair with bonded device case. Log why the cached address is not found. 1149 mEventLogger.setCurrentEvent( 1150 EventCode.DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS); 1151 mEventLogger.logCurrentEventFailed( 1152 mPreferences.getIsDeviceFinishCheckAddressFromCache() 1153 ? new ConnectException(ConnectErrorCode.FAIL_TO_DISCOVERY, 1154 "Failed to discovery") 1155 : new ConnectException( 1156 ConnectErrorCode.DISCOVERY_NOT_FINISHED, 1157 "Discovery not finished")); 1158 Log.i(TAG, "Failed to get cached address due to " 1159 + (mPreferences.getIsDeviceFinishCheckAddressFromCache() 1160 ? "Failed to discovery" 1161 : "Discovery not finished")); 1162 } 1163 } else if (device.getBondState() == BOND_NONE) { 1164 // Pair with new device case, log how many devices with the same model id is in FastPair 1165 // cache already. 1166 mEventLogger.setCurrentEvent(EventCode.PAIR_WITH_NEW_MODEL); 1167 if (mPreferences.getSameModelIdPairedDeviceCount() <= 0) { 1168 mEventLogger.logCurrentEventSucceeded(); 1169 } else { 1170 mEventLogger.logCurrentEventFailed( 1171 new BluetoothGattException( 1172 "Already have this model ID in cache", 1173 GATT_ERROR_CODE_PAIR_WITH_SAME_MODEL_ID_COUNT 1174 + mPreferences.getSameModelIdPairedDeviceCount())); 1175 } 1176 Log.i(TAG, "This device already has " + mPreferences.getSameModelIdPairedDeviceCount() 1177 + " peripheral with the same model Id"); 1178 } 1179 } 1180 1181 /** 1182 * Attempts to directly connect to any supported profile if we're already bonded, this will save 1183 * time over tearing down the bond and recreating it. 1184 */ attemptDirectConnectionIfBonded(BluetoothDevice device, BluetoothAudioPairer pairer)1185 private void attemptDirectConnectionIfBonded(BluetoothDevice device, 1186 BluetoothAudioPairer pairer) 1187 throws PairingException { 1188 if (mPreferences.getSkipConnectingProfiles()) { 1189 if (mPreferences.getCheckBondStateWhenSkipConnectingProfiles() 1190 && device.getBondState() == BluetoothDevice.BOND_BONDED) { 1191 Log.i(TAG, "Skipping connecting to profiles by preferences."); 1192 return; 1193 } 1194 throw new PairingException( 1195 "Skipping connecting to profiles, no direct connection possible."); 1196 } else if (!mPreferences.getAttemptDirectConnectionWhenPreviouslyBonded() 1197 || device.getBondState() != BluetoothDevice.BOND_BONDED) { 1198 throw new PairingException( 1199 "Not previously bonded skipping direct connection, %s", device.getBondState()); 1200 } 1201 short[] supportedProfiles = getSupportedProfiles(device); 1202 mEventLogger.setCurrentEvent(EventCode.DIRECTLY_CONNECTED_TO_PROFILE); 1203 try (ScopedTiming scopedTiming = 1204 new ScopedTiming(mTimingLogger, "Connect to profile directly")) { 1205 attemptConnectProfiles( 1206 pairer, 1207 maskBluetoothAddress(device), 1208 supportedProfiles, 1209 mPreferences.getEnablePairFlowShowUiWithoutProfileConnection() 1210 ? mPreferences.getNumConnectAttempts() 1211 : 1, 1212 mPreferences.getEnablePairingWhileDirectlyConnecting()); 1213 Log.i(TAG, "Directly connected to " + maskBluetoothAddress(device)); 1214 mEventLogger.logCurrentEventSucceeded(); 1215 } catch (PairingException e) { 1216 mEventLogger.logCurrentEventFailed(e); 1217 // Rethrow e so that the exception bubbles up and we continue the normal pairing 1218 // process. 1219 throw e; 1220 } 1221 } 1222 1223 @VisibleForTesting attemptConnectProfiles( BluetoothAudioPairer pairer, String deviceMaskedBluetoothAddress, short[] profiles, int numConnectionAttempts, boolean enablePairingBehavior)1224 void attemptConnectProfiles( 1225 BluetoothAudioPairer pairer, 1226 String deviceMaskedBluetoothAddress, 1227 short[] profiles, 1228 int numConnectionAttempts, 1229 boolean enablePairingBehavior) 1230 throws PairingException { 1231 attemptConnectProfiles( 1232 pairer, 1233 deviceMaskedBluetoothAddress, 1234 profiles, 1235 numConnectionAttempts, 1236 enablePairingBehavior, 1237 new AtomicBoolean(false)); 1238 } 1239 attemptConnectProfiles( BluetoothAudioPairer pairer, String deviceMaskedBluetoothAddress, short[] profiles, int numConnectionAttempts, boolean enablePairingBehavior, AtomicBoolean interruptConnection)1240 private void attemptConnectProfiles( 1241 BluetoothAudioPairer pairer, 1242 String deviceMaskedBluetoothAddress, 1243 short[] profiles, 1244 int numConnectionAttempts, 1245 boolean enablePairingBehavior, 1246 AtomicBoolean interruptConnection) 1247 throws PairingException { 1248 if (mPreferences.getMoreEventLogForQuality()) { 1249 mEventLogger.setCurrentEvent(EventCode.BEFORE_CONNECT_PROFILE); 1250 } 1251 Exception lastException = null; 1252 for (short profile : profiles) { 1253 if (interruptConnection.get()) { 1254 Log.w(TAG, "attemptConnectProfiles interrupted"); 1255 break; 1256 } 1257 if (!mPreferences.isSupportedProfile(profile)) { 1258 Log.w(TAG, "Ignoring unsupported profile=" + profile); 1259 continue; 1260 } 1261 for (int i = 1; i <= numConnectionAttempts; i++) { 1262 if (interruptConnection.get()) { 1263 Log.w(TAG, "attemptConnectProfiles interrupted"); 1264 break; 1265 } 1266 mEventLogger.setCurrentEvent(EventCode.CONNECT_PROFILE); 1267 mEventLogger.setCurrentProfile(profile); 1268 try { 1269 pairer.connect(profile, enablePairingBehavior); 1270 mEventLogger.logCurrentEventSucceeded(); 1271 if (mPreferences.getMoreEventLogForQuality()) { 1272 // For EventCode.BEFORE_CONNECT_PROFILE 1273 mEventLogger.logCurrentEventSucceeded(); 1274 } 1275 // If successful, we're done. 1276 // TODO(b/37167120): Connect to more than one profile. 1277 return; 1278 } catch (InterruptedException 1279 | ReflectionException 1280 | TimeoutException 1281 | ExecutionException 1282 | ConnectException e) { 1283 Log.w(TAG, 1284 "Error connecting to profile=" + profile 1285 + " for device=" + deviceMaskedBluetoothAddress 1286 + " (attempt " + i + " of " + mPreferences 1287 .getNumConnectAttempts(), e); 1288 mEventLogger.logCurrentEventFailed(e); 1289 lastException = e; 1290 } 1291 } 1292 } 1293 if (mPreferences.getMoreEventLogForQuality()) { 1294 // For EventCode.BEFORE_CONNECT_PROFILE 1295 if (lastException != null) { 1296 mEventLogger.logCurrentEventFailed(lastException); 1297 } else { 1298 mEventLogger.logCurrentEventSucceeded(); 1299 } 1300 } 1301 throw new PairingException( 1302 "Unable to connect to any profiles in: %s", Arrays.toString(profiles)); 1303 } 1304 1305 /** 1306 * Checks whether or not an account key should be written to the device and writes it if so. 1307 * This is called after handle notifying the pairedCallback that we've finished pairing, because 1308 * at this point the headset is ready to use. 1309 */ 1310 @Nullable maybeWriteAccountKey(BluetoothDevice device)1311 private SharedSecret maybeWriteAccountKey(BluetoothDevice device) 1312 throws InterruptedException, ExecutionException, TimeoutException, 1313 NoSuchAlgorithmException, 1314 BluetoothException { 1315 if (!sTestMode) { 1316 Locator.get(mContext, FastPairController.class).setShouldUpload(false); 1317 } 1318 if (!shouldWriteAccountKey()) { 1319 // For FastPair 2.0, here should be a subsequent pairing case. 1320 return null; 1321 } 1322 1323 // Check if it should be a subsequent pairing but go through initial pairing. If there is an 1324 // existed paired history found, use the same account key instead of creating a new one. 1325 byte[] accountKey = 1326 mPairedHistoryFinder == null ? null : mPairedHistoryFinder.getExistingAccountKey(); 1327 if (accountKey == null) { 1328 // It is a real initial pairing, generate a new account key for the headset. 1329 try (ScopedTiming scopedTiming1 = 1330 new ScopedTiming(mTimingLogger, "Write account key")) { 1331 accountKey = doWriteAccountKey(createAccountKey(), device.getAddress()); 1332 if (accountKey == null) { 1333 // Without writing account key back to provider, close the connection. 1334 mGattConnectionManager.closeConnection(); 1335 return null; 1336 } 1337 if (!mPreferences.getIsRetroactivePairing()) { 1338 try (ScopedTiming scopedTiming2 = new ScopedTiming(mTimingLogger, 1339 "Start CloudSyncing")) { 1340 // Start to sync to the footprint 1341 Locator.get(mContext, FastPairController.class).setShouldUpload(true); 1342 //mContext.startService(createCloudSyncingIntent(accountKey)); 1343 } catch (SecurityException e) { 1344 Log.w(TAG, "Error adding device.", e); 1345 } 1346 } 1347 } 1348 } else if (shouldWriteAccountKeyForExistingCase(accountKey)) { 1349 // There is an existing account key, but go through initial pairing, and still write the 1350 // existing account key. 1351 doWriteAccountKey(accountKey, device.getAddress()); 1352 } 1353 1354 // When finish writing account key in initial pairing, write new device name back to 1355 // provider. 1356 UUID characteristicUuid = NameCharacteristic.getId(mGattConnectionManager.getConnection()); 1357 if (mPreferences.getEnableNamingCharacteristic() 1358 && mNeedUpdateProviderName 1359 && validateBluetoothGattCharacteristic( 1360 mGattConnectionManager.getConnection(), characteristicUuid)) { 1361 try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, 1362 "WriteNameToProvider")) { 1363 writeNameToProvider(this.mProviderDeviceName, device.getAddress()); 1364 } 1365 } 1366 1367 // When finish writing account key and name back to provider, close the connection. 1368 mGattConnectionManager.closeConnection(); 1369 return SharedSecret.create(accountKey, device.getAddress()); 1370 } 1371 shouldWriteAccountKey()1372 private boolean shouldWriteAccountKey() { 1373 return isWritingAccountKeyEnabled() && isPairingWithAntiSpoofingPublicKey(); 1374 } 1375 isWritingAccountKeyEnabled()1376 private boolean isWritingAccountKeyEnabled() { 1377 return mPreferences.getNumWriteAccountKeyAttempts() > 0; 1378 } 1379 isPairingWithAntiSpoofingPublicKey()1380 private boolean isPairingWithAntiSpoofingPublicKey() { 1381 return isPairingWithAntiSpoofingPublicKey(mPairingKey); 1382 } 1383 isPairingWithAntiSpoofingPublicKey(@ullable byte[] key)1384 private boolean isPairingWithAntiSpoofingPublicKey(@Nullable byte[] key) { 1385 return key != null && key.length == EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH; 1386 } 1387 1388 /** 1389 * Creates and writes an account key to the provided mac address. 1390 */ 1391 @Nullable doWriteAccountKey(byte[] accountKey, String macAddress)1392 private byte[] doWriteAccountKey(byte[] accountKey, String macAddress) 1393 throws InterruptedException, ExecutionException, TimeoutException, BluetoothException { 1394 byte[] localPairingSecret = mPairingSecret; 1395 if (localPairingSecret == null) { 1396 Log.w(TAG, "Pairing secret was null, account key couldn't be encrypted or written."); 1397 return null; 1398 } 1399 if (!mPreferences.getSkipDisconnectingGattBeforeWritingAccountKey()) { 1400 try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, 1401 "Close GATT and sleep")) { 1402 // Make a new connection instead of reusing gattConnection, because this is 1403 // post-pairing and we need an encrypted connection. 1404 mGattConnectionManager.closeConnection(); 1405 // Sleep before re-connecting to gatt, for writing account key, could increase 1406 // stability. 1407 Thread.sleep(mPreferences.getWriteAccountKeySleepMillis()); 1408 } 1409 } 1410 1411 byte[] encryptedKey; 1412 try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Encrypt key")) { 1413 encryptedKey = AesEcbSingleBlockEncryption.encrypt(localPairingSecret, accountKey); 1414 } catch (GeneralSecurityException e) { 1415 Log.w("Failed to encrypt key.", e); 1416 return null; 1417 } 1418 1419 for (int i = 1; i <= mPreferences.getNumWriteAccountKeyAttempts(); i++) { 1420 mEventLogger.setCurrentEvent(EventCode.WRITE_ACCOUNT_KEY); 1421 try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, 1422 "Write key via GATT #" + i)) { 1423 writeAccountKey(encryptedKey, macAddress); 1424 mEventLogger.logCurrentEventSucceeded(); 1425 return accountKey; 1426 } catch (BluetoothException e) { 1427 Log.w("Error writing account key attempt " + i + " of " + mPreferences 1428 .getNumWriteAccountKeyAttempts(), e); 1429 mEventLogger.logCurrentEventFailed(e); 1430 // Retry with a while for stability. 1431 Thread.sleep(mPreferences.getWriteAccountKeySleepMillis()); 1432 } 1433 } 1434 return null; 1435 } 1436 createAccountKey()1437 private byte[] createAccountKey() throws NoSuchAlgorithmException { 1438 return AccountKeyGenerator.createAccountKey(); 1439 } 1440 1441 @VisibleForTesting shouldWriteAccountKeyForExistingCase(byte[] existingAccountKey)1442 boolean shouldWriteAccountKeyForExistingCase(byte[] existingAccountKey) { 1443 if (!mPreferences.getKeepSameAccountKeyWrite()) { 1444 Log.i(TAG, 1445 "The provider has already paired with the account, skip writing account key."); 1446 return false; 1447 } 1448 if (existingAccountKey[0] != AccountKeyCharacteristic.TYPE) { 1449 Log.i(TAG, 1450 "The provider has already paired with the account, but accountKey[0] != 0x04." 1451 + " Forget the device from the account and re-try"); 1452 1453 return false; 1454 } 1455 Log.i(TAG, "The provider has already paired with the account, still write the same account " 1456 + "key."); 1457 return true; 1458 } 1459 1460 /** 1461 * Performs a key-based pairing request handshake to authenticate and get the remote device's 1462 * public address. 1463 * 1464 * @param key is described in {@link #pair(byte[])} 1465 */ 1466 @VisibleForTesting handshakeForKeyBasedPairing(byte[] key)1467 SharedSecret handshakeForKeyBasedPairing(byte[] key) 1468 throws InterruptedException, ExecutionException, TimeoutException, BluetoothException, 1469 GeneralSecurityException, PairingException { 1470 // We may also initialize gattConnectionManager of prepareForHandshake() that will be used 1471 // in registerNotificationForNamePacket(), so we need to call it here. 1472 HandshakeHandler handshakeHandler = prepareForHandshake(); 1473 KeyBasedPairingRequest.Builder keyBasedPairingRequestBuilder = 1474 new KeyBasedPairingRequest.Builder() 1475 .setVerificationData(BluetoothAddress.decode(mBleAddress)); 1476 if (mProviderInitiatesBonding) { 1477 keyBasedPairingRequestBuilder 1478 .addFlag(KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING); 1479 } 1480 // Seeker only request provider device name in initial pairing. 1481 if (mPreferences.getEnableNamingCharacteristic() && isPairingWithAntiSpoofingPublicKey( 1482 key)) { 1483 keyBasedPairingRequestBuilder.addFlag(KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME); 1484 // Register listener to receive name characteristic response from provider. 1485 registerNotificationForNamePacket(); 1486 } 1487 if (mPreferences.getIsRetroactivePairing()) { 1488 keyBasedPairingRequestBuilder 1489 .addFlag(KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR); 1490 keyBasedPairingRequestBuilder.setSeekerPublicAddress( 1491 Preconditions.checkNotNull(BluetoothAddress.getPublicAddress(mContext))); 1492 } 1493 1494 return performHandshakeWithRetryAndSignalLostCheck( 1495 handshakeHandler, key, keyBasedPairingRequestBuilder.build(), /* withRetry= */ 1496 true); 1497 } 1498 1499 /** 1500 * Performs an action-over-BLE request handshake for authentication, i.e. to identify the shared 1501 * secret. The given key should be the account key. 1502 */ handshakeForActionOverBle(byte[] key, @AdditionalDataType int additionalDataType)1503 private SharedSecret handshakeForActionOverBle(byte[] key, 1504 @AdditionalDataType int additionalDataType) 1505 throws InterruptedException, ExecutionException, TimeoutException, BluetoothException, 1506 GeneralSecurityException, PairingException { 1507 HandshakeHandler handshakeHandler = prepareForHandshake(); 1508 return performHandshakeWithRetryAndSignalLostCheck( 1509 handshakeHandler, 1510 key, 1511 new ActionOverBle.Builder() 1512 .setVerificationData(BluetoothAddress.decode(mBleAddress)) 1513 .setAdditionalDataType(additionalDataType) 1514 .build(), 1515 /* withRetry= */ false); 1516 } 1517 prepareForHandshake()1518 private HandshakeHandler prepareForHandshake() { 1519 if (mGattConnectionManager == null) { 1520 mGattConnectionManager = 1521 new GattConnectionManager( 1522 mContext, 1523 mPreferences, 1524 mEventLogger, 1525 mBluetoothAdapter, 1526 this::toggleBluetooth, 1527 mBleAddress, 1528 mTimingLogger, 1529 mFastPairSignalChecker, 1530 isPairingWithAntiSpoofingPublicKey()); 1531 } 1532 if (mHandshakeHandlerForTest != null) { 1533 Log.w(TAG, "Use handshakeHandlerForTest!"); 1534 return verifyNotNull(mHandshakeHandlerForTest); 1535 } 1536 return new HandshakeHandler( 1537 mGattConnectionManager, mBleAddress, mPreferences, mEventLogger, 1538 mFastPairSignalChecker); 1539 } 1540 1541 @VisibleForTesting setHandshakeHandlerForTest(@ullable HandshakeHandler handshakeHandlerForTest)1542 void setHandshakeHandlerForTest(@Nullable HandshakeHandler handshakeHandlerForTest) { 1543 this.mHandshakeHandlerForTest = handshakeHandlerForTest; 1544 } 1545 performHandshakeWithRetryAndSignalLostCheck( HandshakeHandler handshakeHandler, byte[] key, HandshakeMessage handshakeMessage, boolean withRetry)1546 private SharedSecret performHandshakeWithRetryAndSignalLostCheck( 1547 HandshakeHandler handshakeHandler, 1548 byte[] key, 1549 HandshakeMessage handshakeMessage, 1550 boolean withRetry) 1551 throws GeneralSecurityException, ExecutionException, BluetoothException, 1552 InterruptedException, TimeoutException, PairingException { 1553 SharedSecret handshakeResult = 1554 withRetry 1555 ? handshakeHandler.doHandshakeWithRetryAndSignalLostCheck( 1556 key, handshakeMessage, mRescueFromError) 1557 : handshakeHandler.doHandshake(key, handshakeMessage); 1558 // TODO: Try to remove these two global variables, publicAddress and pairingSecret. 1559 mPublicAddress = handshakeResult.getAddress(); 1560 mPairingSecret = handshakeResult.getKey(); 1561 return handshakeResult; 1562 } 1563 toggleBluetooth()1564 private void toggleBluetooth() 1565 throws InterruptedException, ExecutionException, TimeoutException { 1566 if (!mPreferences.getToggleBluetoothOnFailure()) { 1567 return; 1568 } 1569 1570 Log.i(TAG, "Turning Bluetooth off."); 1571 mEventLogger.setCurrentEvent(EventCode.DISABLE_BLUETOOTH); 1572 mBluetoothAdapter.unwrap().disable(); 1573 disableBle(mBluetoothAdapter.unwrap()); 1574 try { 1575 waitForBluetoothState(android.bluetooth.BluetoothAdapter.STATE_OFF); 1576 mEventLogger.logCurrentEventSucceeded(); 1577 } catch (TimeoutException e) { 1578 mEventLogger.logCurrentEventFailed(e); 1579 // Soldier on despite failing to turn off Bluetooth. We can't control whether other 1580 // clients (even inside GCore) kept it enabled in BLE-only mode. 1581 Log.w(TAG, "Bluetooth still on. BluetoothAdapter state=" 1582 + getBleState(mBluetoothAdapter.unwrap()), e); 1583 } 1584 1585 // Note: Intentionally don't re-enable BLE-only mode, because we don't know which app 1586 // enabled it. The client app should listen to Bluetooth events and enable as necessary 1587 // (because the user can toggle at any time; e.g. via Airplane mode). 1588 Log.i(TAG, "Turning Bluetooth on."); 1589 mEventLogger.setCurrentEvent(EventCode.ENABLE_BLUETOOTH); 1590 mBluetoothAdapter.unwrap().enable(); 1591 waitForBluetoothState(android.bluetooth.BluetoothAdapter.STATE_ON); 1592 mEventLogger.logCurrentEventSucceeded(); 1593 } 1594 waitForBluetoothState(int state)1595 private void waitForBluetoothState(int state) 1596 throws TimeoutException, ExecutionException, InterruptedException { 1597 waitForBluetoothStateUsingPolling(state); 1598 } 1599 waitForBluetoothStateUsingPolling(int state)1600 private void waitForBluetoothStateUsingPolling(int state) throws TimeoutException { 1601 // There's a bug where we (pretty often!) never get the broadcast for STATE_ON or STATE_OFF. 1602 // So poll instead. 1603 long start = SystemClock.elapsedRealtime(); 1604 long timeoutMillis = mPreferences.getBluetoothToggleTimeoutSeconds() * 1000L; 1605 while (SystemClock.elapsedRealtime() - start < timeoutMillis) { 1606 if (state == getBleState(mBluetoothAdapter.unwrap())) { 1607 break; 1608 } 1609 SystemClock.sleep(mPreferences.getBluetoothStatePollingMillis()); 1610 } 1611 1612 if (state != getBleState(mBluetoothAdapter.unwrap())) { 1613 throw new TimeoutException( 1614 String.format( 1615 Locale.getDefault(), 1616 "Timed out waiting for state %d, current state is %d", 1617 state, 1618 getBleState(mBluetoothAdapter.unwrap()))); 1619 } 1620 } 1621 getBrEdrHandoverInformation(BluetoothGattConnection connection)1622 private BrEdrHandoverInformation getBrEdrHandoverInformation(BluetoothGattConnection connection) 1623 throws BluetoothException, TdsException, InterruptedException, ExecutionException, 1624 TimeoutException { 1625 Log.i(TAG, "Connecting GATT server to BLE address=" + maskBluetoothAddress(mBleAddress)); 1626 Log.i(TAG, "Telling device to become discoverable"); 1627 mEventLogger.setCurrentEvent(EventCode.BR_EDR_HANDOVER_WRITE_CONTROL_POINT_REQUEST); 1628 ChangeObserver changeObserver = 1629 connection.enableNotification( 1630 TransportDiscoveryService.ID, 1631 TransportDiscoveryService.ControlPointCharacteristic.ID); 1632 connection.writeCharacteristic( 1633 TransportDiscoveryService.ID, 1634 TransportDiscoveryService.ControlPointCharacteristic.ID, 1635 TDS_CONTROL_POINT_REQUEST); 1636 1637 byte[] response = 1638 changeObserver.waitForUpdate( 1639 TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); 1640 @ResultCode int resultCode = fromTdsControlPointIndication(response); 1641 if (resultCode != ResultCode.SUCCESS) { 1642 throw new TdsException( 1643 BrEdrHandoverErrorCode.CONTROL_POINT_RESULT_CODE_NOT_SUCCESS, 1644 "TDS Control Point result code (%s) was not success in response %s", 1645 resultCode, 1646 base16().lowerCase().encode(response)); 1647 } 1648 mEventLogger.logCurrentEventSucceeded(); 1649 return new BrEdrHandoverInformation( 1650 getAddressFromBrEdrConnection(connection), 1651 getProfilesFromBrEdrConnection(connection)); 1652 } 1653 getAddressFromBrEdrConnection(BluetoothGattConnection connection)1654 private byte[] getAddressFromBrEdrConnection(BluetoothGattConnection connection) 1655 throws BluetoothException, TdsException { 1656 Log.i(TAG, "Getting Bluetooth MAC"); 1657 mEventLogger.setCurrentEvent(EventCode.BR_EDR_HANDOVER_READ_BLUETOOTH_MAC); 1658 byte[] brHandoverData = 1659 connection.readCharacteristic( 1660 TransportDiscoveryService.ID, 1661 to128BitUuid(mPreferences.getBrHandoverDataCharacteristicId())); 1662 if (brHandoverData == null || brHandoverData.length < 7) { 1663 throw new TdsException( 1664 BrEdrHandoverErrorCode.BLUETOOTH_MAC_INVALID, 1665 "Bluetooth MAC not contained in BR handover data: %s", 1666 brHandoverData != null ? base16().lowerCase().encode(brHandoverData) 1667 : "(none)"); 1668 } 1669 byte[] bluetoothAddress = 1670 new Bytes.Value(Arrays.copyOfRange(brHandoverData, 1, 7), ByteOrder.LITTLE_ENDIAN) 1671 .getBytes(ByteOrder.BIG_ENDIAN); 1672 mEventLogger.logCurrentEventSucceeded(); 1673 return bluetoothAddress; 1674 } 1675 getProfilesFromBrEdrConnection(BluetoothGattConnection connection)1676 private short[] getProfilesFromBrEdrConnection(BluetoothGattConnection connection) { 1677 mEventLogger.setCurrentEvent(EventCode.BR_EDR_HANDOVER_READ_TRANSPORT_BLOCK); 1678 try { 1679 byte[] transportBlock = 1680 connection.readDescriptor( 1681 TransportDiscoveryService.ID, 1682 to128BitUuid(mPreferences.getBluetoothSigDataCharacteristicId()), 1683 to128BitUuid(mPreferences.getBrTransportBlockDataDescriptorId())); 1684 Log.i(TAG, "Got transport block: " + base16().lowerCase().encode(transportBlock)); 1685 short[] profiles = getSupportedProfiles(transportBlock); 1686 mEventLogger.logCurrentEventSucceeded(); 1687 return profiles; 1688 } catch (BluetoothException | TdsException | ParseException e) { 1689 Log.w(TAG, "Failed to get supported profiles from transport block.", e); 1690 mEventLogger.logCurrentEventFailed(e); 1691 } 1692 return new short[0]; 1693 } 1694 1695 @VisibleForTesting writeNameToProvider(@ullable String deviceName, @Nullable String address)1696 boolean writeNameToProvider(@Nullable String deviceName, @Nullable String address) 1697 throws InterruptedException, TimeoutException, ExecutionException { 1698 if (deviceName == null || address == null) { 1699 Log.i(TAG, "writeNameToProvider fail because provider name or address is null."); 1700 return false; 1701 } 1702 if (mPairingSecret == null) { 1703 Log.i(TAG, "writeNameToProvider fail because no pairingSecret."); 1704 return false; 1705 } 1706 byte[] encryptedDeviceNamePacket; 1707 try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Encode device name")) { 1708 encryptedDeviceNamePacket = 1709 NamingEncoder.encodeNamingPacket(mPairingSecret, deviceName); 1710 } catch (GeneralSecurityException e) { 1711 Log.w(TAG, "Failed to encrypt device name.", e); 1712 return false; 1713 } 1714 1715 for (int i = 1; i <= mPreferences.getNumWriteAccountKeyAttempts(); i++) { 1716 mEventLogger.setCurrentEvent(EventCode.WRITE_DEVICE_NAME); 1717 try { 1718 writeDeviceName(encryptedDeviceNamePacket, address); 1719 mEventLogger.logCurrentEventSucceeded(); 1720 return true; 1721 } catch (BluetoothException e) { 1722 Log.w(TAG, "Error writing name attempt " + i + " of " 1723 + mPreferences.getNumWriteAccountKeyAttempts()); 1724 mEventLogger.logCurrentEventFailed(e); 1725 // Reuses the existing preference because the same usage. 1726 Thread.sleep(mPreferences.getWriteAccountKeySleepMillis()); 1727 } 1728 } 1729 return false; 1730 } 1731 writeAccountKey(byte[] encryptedAccountKey, String address)1732 private void writeAccountKey(byte[] encryptedAccountKey, String address) 1733 throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { 1734 Log.i(TAG, "Writing account key to address=" + maskBluetoothAddress(address)); 1735 BluetoothGattConnection connection = mGattConnectionManager.getConnection(); 1736 connection.setOperationTimeout( 1737 TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); 1738 UUID characteristicUuid = AccountKeyCharacteristic.getId(connection); 1739 connection.writeCharacteristic(FastPairService.ID, characteristicUuid, encryptedAccountKey); 1740 Log.i(TAG, 1741 "Finished writing encrypted account key=" + base16().encode(encryptedAccountKey)); 1742 } 1743 writeDeviceName(byte[] naming, String address)1744 private void writeDeviceName(byte[] naming, String address) 1745 throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { 1746 Log.i(TAG, "Writing new device name to address=" + maskBluetoothAddress(address)); 1747 BluetoothGattConnection connection = mGattConnectionManager.getConnection(); 1748 connection.setOperationTimeout( 1749 TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); 1750 UUID characteristicUuid = NameCharacteristic.getId(connection); 1751 connection.writeCharacteristic(FastPairService.ID, characteristicUuid, naming); 1752 Log.i(TAG, "Finished writing new device name=" + base16().encode(naming)); 1753 } 1754 1755 /** 1756 * Reads firmware version after write account key to provider since simulator is more stable to 1757 * read firmware version in initial gatt connection. This function will also read firmware when 1758 * detect bloomfilter. Need to verify this after real device come out. TODO(b/130592473) 1759 */ 1760 @Nullable readFirmwareVersion()1761 public String readFirmwareVersion() 1762 throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { 1763 if (!TextUtils.isEmpty(sInitialConnectionFirmwareVersion)) { 1764 String result = sInitialConnectionFirmwareVersion; 1765 sInitialConnectionFirmwareVersion = null; 1766 return result; 1767 } 1768 if (mGattConnectionManager == null) { 1769 mGattConnectionManager = 1770 new GattConnectionManager( 1771 mContext, 1772 mPreferences, 1773 mEventLogger, 1774 mBluetoothAdapter, 1775 this::toggleBluetooth, 1776 mBleAddress, 1777 mTimingLogger, 1778 mFastPairSignalChecker, 1779 /* setMtu= */ true); 1780 mGattConnectionManager.closeConnection(); 1781 } 1782 if (sTestMode) { 1783 return null; 1784 } 1785 BluetoothGattConnection connection = mGattConnectionManager.getConnection(); 1786 connection.setOperationTimeout( 1787 TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); 1788 1789 try { 1790 String firmwareVersion = 1791 new String( 1792 connection.readCharacteristic( 1793 FastPairService.ID, 1794 to128BitUuid( 1795 mPreferences.getFirmwareVersionCharacteristicId()))); 1796 Log.i(TAG, "FastPair: Got the firmware info version number = " + firmwareVersion); 1797 mGattConnectionManager.closeConnection(); 1798 return firmwareVersion; 1799 } catch (BluetoothException e) { 1800 Log.i(TAG, "FastPair: can't read firmware characteristic.", e); 1801 mGattConnectionManager.closeConnection(); 1802 return null; 1803 } 1804 } 1805 1806 @VisibleForTesting 1807 @Nullable getInitialConnectionFirmware()1808 String getInitialConnectionFirmware() { 1809 return sInitialConnectionFirmwareVersion; 1810 } 1811 registerNotificationForNamePacket()1812 private void registerNotificationForNamePacket() 1813 throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { 1814 Log.i(TAG, 1815 "register for the device name response from address=" + maskBluetoothAddress( 1816 mBleAddress)); 1817 1818 BluetoothGattConnection gattConnection = mGattConnectionManager.getConnection(); 1819 gattConnection.setOperationTimeout( 1820 TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); 1821 try { 1822 mDeviceNameReceiver = new DeviceNameReceiver(gattConnection); 1823 } catch (BluetoothException e) { 1824 Log.i(TAG, "Can't register for device name response, no naming characteristic."); 1825 return; 1826 } 1827 } 1828 getSupportedProfiles(BluetoothDevice device)1829 private short[] getSupportedProfiles(BluetoothDevice device) { 1830 short[] supportedProfiles = getCachedUuids(device); 1831 if (supportedProfiles.length == 0 && mPreferences.getNumSdpAttemptsAfterBonded() > 0) { 1832 supportedProfiles = 1833 attemptGetBluetoothClassicProfiles(device, 1834 mPreferences.getNumSdpAttemptsAfterBonded()); 1835 } 1836 if (supportedProfiles.length == 0) { 1837 supportedProfiles = Constants.getSupportedProfiles(); 1838 Log.w(TAG, "Attempting to connect constants profiles, " 1839 + Arrays.toString(supportedProfiles)); 1840 } else { 1841 Log.i(TAG, 1842 "Attempting to connect device profiles, " + Arrays.toString(supportedProfiles)); 1843 } 1844 return supportedProfiles; 1845 } 1846 getSupportedProfiles(byte[] transportBlock)1847 private static short[] getSupportedProfiles(byte[] transportBlock) 1848 throws TdsException, ParseException { 1849 if (transportBlock == null || transportBlock.length < 4) { 1850 throw new TdsException( 1851 BrEdrHandoverErrorCode.TRANSPORT_BLOCK_INVALID, 1852 "Transport Block null or too short: %s", 1853 base16().lowerCase().encode(transportBlock)); 1854 } 1855 int transportDataLength = transportBlock[2]; 1856 if (transportBlock.length < 3 + transportDataLength) { 1857 throw new TdsException( 1858 BrEdrHandoverErrorCode.TRANSPORT_BLOCK_INVALID, 1859 "Transport Block has wrong length byte: %s", 1860 base16().lowerCase().encode(transportBlock)); 1861 } 1862 byte[] transportData = Arrays.copyOfRange(transportBlock, 3, 3 + transportDataLength); 1863 for (Ltv ltv : Ltv.parse(transportData)) { 1864 int uuidLength = uuidLength(ltv.mType); 1865 // We currently only support a single list of 2-byte UUIDs. 1866 // TODO(b/37539535): Support multiple lists, and longer (32-bit, 128-bit) IDs? 1867 if (uuidLength == 2) { 1868 return toShorts(ByteOrder.LITTLE_ENDIAN, ltv.mValue); 1869 } 1870 } 1871 return new short[0]; 1872 } 1873 1874 /** 1875 * Returns 0 if the type is not one of the UUID list types; otherwise returns length in bytes. 1876 */ uuidLength(byte dataType)1877 private static int uuidLength(byte dataType) { 1878 switch (dataType) { 1879 case TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE: 1880 return 2; 1881 case TransportDiscoveryService.SERVICE_UUIDS_32_BIT_LIST_TYPE: 1882 return 4; 1883 case TransportDiscoveryService.SERVICE_UUIDS_128_BIT_LIST_TYPE: 1884 return 16; 1885 default: 1886 return 0; 1887 } 1888 } 1889 attemptGetBluetoothClassicProfiles(BluetoothDevice device, int numSdpAttempts)1890 private short[] attemptGetBluetoothClassicProfiles(BluetoothDevice device, int numSdpAttempts) { 1891 // The docs say that if fetchUuidsWithSdp() has an error or "takes a long time", we get an 1892 // intent containing only the stuff in the cache (i.e. nothing). Retry a few times. 1893 short[] supportedProfiles = null; 1894 for (int i = 1; i <= numSdpAttempts; i++) { 1895 mEventLogger.setCurrentEvent(EventCode.GET_PROFILES_VIA_SDP); 1896 try (ScopedTiming scopedTiming = 1897 new ScopedTiming(mTimingLogger, 1898 "Get BR/EDR handover information via SDP #" + i)) { 1899 supportedProfiles = getSupportedProfilesViaBluetoothClassic(device); 1900 } catch (ExecutionException | InterruptedException | TimeoutException e) { 1901 // Ignores and retries if needed. 1902 } 1903 if (supportedProfiles != null && supportedProfiles.length != 0) { 1904 mEventLogger.logCurrentEventSucceeded(); 1905 break; 1906 } else { 1907 mEventLogger.logCurrentEventFailed(new TimeoutException()); 1908 Log.w(TAG, "SDP returned no UUIDs from " + maskBluetoothAddress(device.getAddress()) 1909 + ", assuming timeout (attempt " + i + " of " + numSdpAttempts + ")."); 1910 } 1911 } 1912 return (supportedProfiles == null) ? new short[0] : supportedProfiles; 1913 } 1914 getSupportedProfilesViaBluetoothClassic(BluetoothDevice device)1915 private short[] getSupportedProfilesViaBluetoothClassic(BluetoothDevice device) 1916 throws ExecutionException, InterruptedException, TimeoutException { 1917 Log.i(TAG, "Getting supported profiles via SDP (Bluetooth Classic) for " 1918 + maskBluetoothAddress(device.getAddress())); 1919 try (DeviceIntentReceiver supportedProfilesReceiver = 1920 DeviceIntentReceiver.oneShotReceiver( 1921 mContext, mPreferences, device, BluetoothDevice.ACTION_UUID)) { 1922 device.fetchUuidsWithSdp(); 1923 supportedProfilesReceiver.await(mPreferences.getSdpTimeoutSeconds(), TimeUnit.SECONDS); 1924 } 1925 return getCachedUuids(device); 1926 } 1927 getCachedUuids(BluetoothDevice device)1928 private static short[] getCachedUuids(BluetoothDevice device) { 1929 ParcelUuid[] parcelUuids = device.getUuids(); 1930 Log.i(TAG, "Got supported UUIDs: " + Arrays.toString(parcelUuids)); 1931 if (parcelUuids == null) { 1932 // The OS can return null. 1933 parcelUuids = new ParcelUuid[0]; 1934 } 1935 1936 List<Short> shortUuids = new ArrayList<>(parcelUuids.length); 1937 for (ParcelUuid parcelUuid : parcelUuids) { 1938 UUID uuid = parcelUuid.getUuid(); 1939 if (BluetoothUuids.is16BitUuid(uuid)) { 1940 shortUuids.add(get16BitUuid(uuid)); 1941 } 1942 } 1943 return Shorts.toArray(shortUuids); 1944 } 1945 callbackOnPaired()1946 private void callbackOnPaired() { 1947 if (mPairedCallback != null) { 1948 mPairedCallback.onPaired(mPublicAddress != null ? mPublicAddress : mBleAddress); 1949 } 1950 } 1951 callbackOnGetAddress(String address)1952 private void callbackOnGetAddress(String address) { 1953 if (mOnGetBluetoothAddressCallback != null) { 1954 mOnGetBluetoothAddressCallback.onGetBluetoothAddress(address); 1955 } 1956 } 1957 validateBluetoothGattCharacteristic( BluetoothGattConnection connection, UUID characteristicUUID)1958 private boolean validateBluetoothGattCharacteristic( 1959 BluetoothGattConnection connection, UUID characteristicUUID) { 1960 try (ScopedTiming scopedTiming = 1961 new ScopedTiming(mTimingLogger, "Get service characteristic list")) { 1962 List<BluetoothGattCharacteristic> serviceCharacteristicList = 1963 connection.getService(FastPairService.ID).getCharacteristics(); 1964 for (BluetoothGattCharacteristic characteristic : serviceCharacteristicList) { 1965 if (characteristicUUID.equals(characteristic.getUuid())) { 1966 Log.i(TAG, "characteristic is exists, uuid = " + characteristicUUID); 1967 return true; 1968 } 1969 } 1970 } catch (BluetoothException e) { 1971 Log.w(TAG, "Can't get service characteristic list.", e); 1972 } 1973 Log.i(TAG, "can't find characteristic, uuid = " + characteristicUUID); 1974 return false; 1975 } 1976 1977 // This method is only for testing to make test method block until get name response or time 1978 // out. 1979 /** 1980 * Set name response countdown latch. 1981 */ setNameResponseCountDownLatch(CountDownLatch countDownLatch)1982 public void setNameResponseCountDownLatch(CountDownLatch countDownLatch) { 1983 if (mDeviceNameReceiver != null) { 1984 mDeviceNameReceiver.setCountDown(countDownLatch); 1985 Log.v(TAG, "set up nameResponseCountDown"); 1986 } 1987 } 1988 getBleState(android.bluetooth.BluetoothAdapter bluetoothAdapter)1989 private static int getBleState(android.bluetooth.BluetoothAdapter bluetoothAdapter) { 1990 // Can't use the public isLeEnabled() API, because it returns false for 1991 // STATE_BLE_TURNING_(ON|OFF). So if we assume false == STATE_OFF, that can be 1992 // very wrong. 1993 return getLeState(bluetoothAdapter); 1994 } 1995 1996 @VisibleForTesting getLeState(android.bluetooth.BluetoothAdapter adapter)1997 static int getLeState(android.bluetooth.BluetoothAdapter adapter) { 1998 try { 1999 return (Integer) Reflect.on(adapter).withMethod("getLeState").get(); 2000 } catch (ReflectionException e) { 2001 Log.i(TAG, "Can't call getLeState", e); 2002 } 2003 return adapter.getState(); 2004 } 2005 disableBle(android.bluetooth.BluetoothAdapter adapter)2006 private static void disableBle(android.bluetooth.BluetoothAdapter adapter) { 2007 adapter.disableBLE(); 2008 } 2009 2010 /** 2011 * Handle the searching of Fast Pair history. Since there is only one public address using 2012 * during Fast Pair connection, {@link #isInPairedHistory(String)} only needs to be called once, 2013 * then the result is kept, and call {@link #getExistingAccountKey()} to get the result. 2014 */ 2015 @VisibleForTesting 2016 static final class FastPairHistoryFinder { 2017 2018 private @Nullable 2019 byte[] mExistingAccountKey; 2020 @Nullable 2021 private final List<FastPairHistoryItem> mHistoryItems; 2022 FastPairHistoryFinder(List<FastPairHistoryItem> historyItems)2023 FastPairHistoryFinder(List<FastPairHistoryItem> historyItems) { 2024 this.mHistoryItems = historyItems; 2025 } 2026 2027 @WorkerThread 2028 @VisibleForTesting isInPairedHistory(String publicAddress)2029 boolean isInPairedHistory(String publicAddress) { 2030 if (mHistoryItems == null || mHistoryItems.isEmpty()) { 2031 return false; 2032 } 2033 for (FastPairHistoryItem item : mHistoryItems) { 2034 if (item.isMatched(BluetoothAddress.decode(publicAddress))) { 2035 mExistingAccountKey = item.accountKey().toByteArray(); 2036 return true; 2037 } 2038 } 2039 return false; 2040 } 2041 2042 // This function should be called after isInPairedHistory(). Or it will just return null. 2043 @WorkerThread 2044 @VisibleForTesting 2045 @Nullable getExistingAccountKey()2046 byte[] getExistingAccountKey() { 2047 return mExistingAccountKey; 2048 } 2049 } 2050 2051 private static final class DeviceNameReceiver { 2052 2053 @GuardedBy("this") 2054 private @Nullable 2055 byte[] mEncryptedResponse; 2056 2057 @GuardedBy("this") 2058 @Nullable 2059 private String mDecryptedDeviceName; 2060 2061 @Nullable 2062 private CountDownLatch mResponseCountDown; 2063 DeviceNameReceiver(BluetoothGattConnection gattConnection)2064 DeviceNameReceiver(BluetoothGattConnection gattConnection) throws BluetoothException { 2065 UUID characteristicUuid = NameCharacteristic.getId(gattConnection); 2066 ChangeObserver observer = 2067 gattConnection.enableNotification(FastPairService.ID, characteristicUuid); 2068 observer.setListener( 2069 (byte[] value) -> { 2070 synchronized (DeviceNameReceiver.this) { 2071 Log.i(TAG, "DeviceNameReceiver: device name response size = " 2072 + value.length); 2073 // We don't decrypt it here because we may not finish handshaking and 2074 // the pairing 2075 // secret is not available. 2076 mEncryptedResponse = value; 2077 } 2078 // For testing to know we get the device name from provider. 2079 if (mResponseCountDown != null) { 2080 mResponseCountDown.countDown(); 2081 Log.v(TAG, "Finish nameResponseCountDown."); 2082 } 2083 }); 2084 } 2085 setCountDown(CountDownLatch countDownLatch)2086 void setCountDown(CountDownLatch countDownLatch) { 2087 this.mResponseCountDown = countDownLatch; 2088 } 2089 getParsedResult(byte[] secret)2090 synchronized @Nullable String getParsedResult(byte[] secret) { 2091 if (mDecryptedDeviceName != null) { 2092 return mDecryptedDeviceName; 2093 } 2094 if (mEncryptedResponse == null) { 2095 Log.i(TAG, "DeviceNameReceiver: no device name sent from the Provider."); 2096 return null; 2097 } 2098 try { 2099 mDecryptedDeviceName = NamingEncoder.decodeNamingPacket(secret, mEncryptedResponse); 2100 Log.i(TAG, "DeviceNameReceiver: decrypted provider's name from naming response, " 2101 + "name = " + mDecryptedDeviceName); 2102 } catch (GeneralSecurityException e) { 2103 Log.w(TAG, "DeviceNameReceiver: fail to parse the NameCharacteristic from provider" 2104 + ".", e); 2105 return null; 2106 } 2107 return mDecryptedDeviceName; 2108 } 2109 } 2110 checkFastPairSignal( FastPairSignalChecker fastPairSignalChecker, String currentAddress, Exception originalException)2111 static void checkFastPairSignal( 2112 FastPairSignalChecker fastPairSignalChecker, 2113 String currentAddress, 2114 Exception originalException) 2115 throws SignalLostException, SignalRotatedException { 2116 String newAddress = fastPairSignalChecker.getValidAddressForModelId(currentAddress); 2117 if (TextUtils.isEmpty(newAddress)) { 2118 throw new SignalLostException("Signal lost", originalException); 2119 } else if (!Ascii.equalsIgnoreCase(currentAddress, newAddress)) { 2120 throw new SignalRotatedException("Address rotated", newAddress, originalException); 2121 } 2122 } 2123 2124 @VisibleForTesting getPreferences()2125 public Preferences getPreferences() { 2126 return mPreferences; 2127 } 2128 } 2129