• 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 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