• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 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.bluetooth.bass_client;
18 
19 import static android.Manifest.permission.BLUETOOTH_CONNECT;
20 
21 import android.annotation.Nullable;
22 import android.bluetooth.BluetoothAdapter;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothGatt;
25 import android.bluetooth.BluetoothGattCallback;
26 import android.bluetooth.BluetoothGattCharacteristic;
27 import android.bluetooth.BluetoothGattDescriptor;
28 import android.bluetooth.BluetoothGattService;
29 import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
30 import android.bluetooth.BluetoothLeAudioContentMetadata;
31 import android.bluetooth.BluetoothLeBroadcastAssistant;
32 import android.bluetooth.BluetoothLeBroadcastChannel;
33 import android.bluetooth.BluetoothLeBroadcastMetadata;
34 import android.bluetooth.BluetoothLeBroadcastReceiveState;
35 import android.bluetooth.BluetoothLeBroadcastSubgroup;
36 import android.bluetooth.BluetoothProfile;
37 import android.bluetooth.BluetoothStatusCodes;
38 import android.bluetooth.BluetoothUtils;
39 import android.bluetooth.BluetoothUtils.TypeValueEntry;
40 import android.bluetooth.le.PeriodicAdvertisingCallback;
41 import android.bluetooth.le.PeriodicAdvertisingManager;
42 import android.bluetooth.le.PeriodicAdvertisingReport;
43 import android.bluetooth.le.ScanRecord;
44 import android.bluetooth.le.ScanResult;
45 import android.content.Intent;
46 import android.os.Binder;
47 import android.os.Looper;
48 import android.os.Message;
49 import android.os.ParcelUuid;
50 import android.provider.DeviceConfig;
51 import android.util.Log;
52 
53 import com.android.bluetooth.BluetoothMethodProxy;
54 import com.android.bluetooth.Utils;
55 import com.android.bluetooth.btservice.ProfileService;
56 import com.android.bluetooth.btservice.ServiceFactory;
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.internal.util.State;
59 import com.android.internal.util.StateMachine;
60 
61 import java.io.ByteArrayOutputStream;
62 import java.io.FileDescriptor;
63 import java.io.PrintWriter;
64 import java.io.StringWriter;
65 import java.nio.charset.StandardCharsets;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.Collection;
69 import java.util.HashMap;
70 import java.util.Iterator;
71 import java.util.List;
72 import java.util.Map;
73 import java.util.Scanner;
74 import java.util.UUID;
75 import java.util.stream.IntStream;
76 
77 @VisibleForTesting
78 public class BassClientStateMachine extends StateMachine {
79     private static final String TAG = "BassClientStateMachine";
80     @VisibleForTesting
81     static final byte[] REMOTE_SCAN_STOP = {00};
82     @VisibleForTesting
83     static final byte[] REMOTE_SCAN_START = {01};
84     private static final byte OPCODE_ADD_SOURCE = 0x02;
85     private static final byte OPCODE_UPDATE_SOURCE = 0x03;
86     private static final byte OPCODE_SET_BCAST_PIN = 0x04;
87     private static final byte OPCODE_REMOVE_SOURCE = 0x05;
88     private static final int ADD_SOURCE_FIXED_LENGTH = 16;
89     private static final int UPDATE_SOURCE_FIXED_LENGTH = 6;
90 
91     static final int CONNECT = 1;
92     static final int DISCONNECT = 2;
93     static final int CONNECTION_STATE_CHANGED = 3;
94     static final int GATT_TXN_PROCESSED = 4;
95     static final int READ_BASS_CHARACTERISTICS = 5;
96     static final int START_SCAN_OFFLOAD = 6;
97     static final int STOP_SCAN_OFFLOAD = 7;
98     static final int SELECT_BCAST_SOURCE = 8;
99     static final int ADD_BCAST_SOURCE = 9;
100     static final int UPDATE_BCAST_SOURCE = 10;
101     static final int SET_BCAST_CODE = 11;
102     static final int REMOVE_BCAST_SOURCE = 12;
103     static final int GATT_TXN_TIMEOUT = 13;
104     static final int PSYNC_ACTIVE_TIMEOUT = 14;
105     static final int CONNECT_TIMEOUT = 15;
106 
107     // NOTE: the value is not "final" - it is modified in the unit tests
108     @VisibleForTesting
109     private int mConnectTimeoutMs;
110 
111     // Type of argument for set broadcast code operation
112     static final int ARGTYPE_METADATA = 1;
113     static final int ARGTYPE_RCVSTATE = 2;
114 
115     /*key is combination of sourceId, Address and advSid for this hashmap*/
116     private final Map<Integer, BluetoothLeBroadcastReceiveState>
117             mBluetoothLeBroadcastReceiveStates =
118             new HashMap<Integer, BluetoothLeBroadcastReceiveState>();
119     private final Map<Integer, BluetoothLeBroadcastMetadata> mCurrentMetadata = new HashMap();
120     private final Disconnected mDisconnected = new Disconnected();
121     private final Connected mConnected = new Connected();
122     private final Connecting mConnecting = new Connecting();
123     private final ConnectedProcessing mConnectedProcessing = new ConnectedProcessing();
124     @VisibleForTesting
125     final List<BluetoothGattCharacteristic> mBroadcastCharacteristics =
126             new ArrayList<BluetoothGattCharacteristic>();
127     @VisibleForTesting
128     BluetoothDevice mDevice;
129 
130     private boolean mIsAllowedList = false;
131     private int mLastConnectionState = -1;
132     @VisibleForTesting
133     boolean mMTUChangeRequested = false;
134     @VisibleForTesting
135     boolean mDiscoveryInitiated = false;
136     @VisibleForTesting
137     BassClientService mService;
138     @VisibleForTesting
139     BluetoothGattCharacteristic mBroadcastScanControlPoint;
140     private final Map<Integer, Boolean> mFirstTimeBisDiscoveryMap;
141     private int mPASyncRetryCounter = 0;
142     private ScanResult mScanRes = null;
143     @VisibleForTesting
144     int mNumOfBroadcastReceiverStates = 0;
145     private BluetoothAdapter mBluetoothAdapter =
146             BluetoothAdapter.getDefaultAdapter();
147     private ServiceFactory mFactory = new ServiceFactory();
148     @VisibleForTesting
149     int mPendingOperation = -1;
150     @VisibleForTesting
151     byte mPendingSourceId = -1;
152     @VisibleForTesting
153     BluetoothLeBroadcastMetadata mPendingMetadata = null;
154     private BluetoothLeBroadcastMetadata mSetBroadcastPINMetadata = null;
155     @VisibleForTesting
156     boolean mSetBroadcastCodePending = false;
157     private final Map<Integer, Boolean> mPendingRemove = new HashMap();
158     // Psync and PAST interfaces
159     private PeriodicAdvertisingManager mPeriodicAdvManager;
160     @VisibleForTesting
161     boolean mAutoTriggered = false;
162     @VisibleForTesting
163     boolean mNoStopScanOffload = false;
164     private boolean mDefNoPAS = false;
165     private boolean mForceSB = false;
166     private int mBroadcastSourceIdLength = 3;
167     @VisibleForTesting
168     byte mNextSourceId = 0;
169     private boolean mAllowReconnect = false;
170     @VisibleForTesting
171     BluetoothGattTestableWrapper mBluetoothGatt = null;
172     BluetoothGattCallback mGattCallback = null;
173 
BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper, int connectTimeoutMs)174     BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper,
175             int connectTimeoutMs) {
176         super(TAG + "(" + device.toString() + ")", looper);
177         mDevice = device;
178         mService = svc;
179         mConnectTimeoutMs = connectTimeoutMs;
180         addState(mDisconnected);
181         addState(mConnected);
182         addState(mConnecting);
183         addState(mConnectedProcessing);
184         setInitialState(mDisconnected);
185         // PSYNC and PAST instances
186         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
187         if (mBluetoothAdapter != null) {
188             mPeriodicAdvManager = mBluetoothAdapter.getPeriodicAdvertisingManager();
189         }
190         mFirstTimeBisDiscoveryMap = new HashMap<Integer, Boolean>();
191         long token = Binder.clearCallingIdentity();
192         mIsAllowedList = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
193                 "persist.vendor.service.bt.wl", true);
194         mDefNoPAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
195                 "persist.vendor.service.bt.defNoPAS", false);
196         mForceSB = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
197                 "persist.vendor.service.bt.forceSB", false);
198         Binder.restoreCallingIdentity(token);
199     }
200 
make(BluetoothDevice device, BassClientService svc, Looper looper)201     static BassClientStateMachine make(BluetoothDevice device,
202             BassClientService svc, Looper looper) {
203         Log.d(TAG, "make for device " + device);
204         BassClientStateMachine BassclientSm = new BassClientStateMachine(device, svc, looper,
205                 BassConstants.CONNECT_TIMEOUT_MS);
206         BassclientSm.start();
207         return BassclientSm;
208     }
209 
destroy(BassClientStateMachine stateMachine)210     static void destroy(BassClientStateMachine stateMachine) {
211         Log.i(TAG, "destroy");
212         if (stateMachine == null) {
213             Log.w(TAG, "destroy(), stateMachine is null");
214             return;
215         }
216         stateMachine.doQuit();
217         stateMachine.cleanup();
218     }
219 
doQuit()220     public void doQuit() {
221         log("doQuit for device " + mDevice);
222         quitNow();
223     }
224 
cleanup()225     public void cleanup() {
226         log("cleanup for device " + mDevice);
227         clearCharsCache();
228 
229         if (mBluetoothGatt != null) {
230             log("disconnect gatt");
231             mBluetoothGatt.disconnect();
232             mBluetoothGatt.close();
233             mBluetoothGatt = null;
234             mGattCallback = null;
235         }
236         mPendingOperation = -1;
237         mPendingSourceId = -1;
238         mPendingMetadata = null;
239         mCurrentMetadata.clear();
240         mPendingRemove.clear();
241     }
242 
hasPendingSourceOperation()243     Boolean hasPendingSourceOperation() {
244         return mPendingMetadata != null;
245     }
246 
getCurrentBroadcastMetadata(Integer sourceId)247     BluetoothLeBroadcastMetadata getCurrentBroadcastMetadata(Integer sourceId) {
248         return mCurrentMetadata.getOrDefault(sourceId, null);
249     }
250 
setCurrentBroadcastMetadata(Integer sourceId, BluetoothLeBroadcastMetadata metadata)251     private void setCurrentBroadcastMetadata(Integer sourceId,
252             BluetoothLeBroadcastMetadata metadata) {
253         if (metadata != null) {
254             mCurrentMetadata.put(sourceId, metadata);
255         } else {
256             mCurrentMetadata.remove(sourceId);
257         }
258     }
259 
isPendingRemove(Integer sourceId)260     boolean isPendingRemove(Integer sourceId) {
261         return mPendingRemove.getOrDefault(sourceId, false);
262     }
263 
setPendingRemove(Integer sourceId, boolean remove)264     private void setPendingRemove(Integer sourceId, boolean remove) {
265         if (remove) {
266             mPendingRemove.put(sourceId, remove);
267         } else {
268             mPendingRemove.remove(sourceId);
269         }
270     }
271 
getBroadcastReceiveStateForSourceDevice( BluetoothDevice srcDevice)272     BluetoothLeBroadcastReceiveState getBroadcastReceiveStateForSourceDevice(
273             BluetoothDevice srcDevice) {
274         List<BluetoothLeBroadcastReceiveState> currentSources = getAllSources();
275         BluetoothLeBroadcastReceiveState state = null;
276         for (int i = 0; i < currentSources.size(); i++) {
277             BluetoothDevice device = currentSources.get(i).getSourceDevice();
278             if (device != null && device.equals(srcDevice)) {
279                 state = currentSources.get(i);
280                 Log.e(TAG,
281                         "getBroadcastReceiveStateForSourceDevice: returns for: "
282                                 + srcDevice + "&srcInfo" + state);
283                 return state;
284             }
285         }
286         return null;
287     }
288 
getBroadcastReceiveStateForSourceId(int sourceId)289     BluetoothLeBroadcastReceiveState getBroadcastReceiveStateForSourceId(int sourceId) {
290         List<BluetoothLeBroadcastReceiveState> currentSources = getAllSources();
291         for (int i = 0; i < currentSources.size(); i++) {
292             if (sourceId == currentSources.get(i).getSourceId()) {
293                 return currentSources.get(i);
294             }
295         }
296         return null;
297     }
298 
parseBaseData(BluetoothDevice device, int syncHandle, byte[] serviceData)299     void parseBaseData(BluetoothDevice device, int syncHandle, byte[] serviceData) {
300         log("parseBaseData" + Arrays.toString(serviceData));
301         BaseData base = BaseData.parseBaseData(serviceData);
302         if (base != null) {
303             mService.updateBase(syncHandle, base);
304             base.print();
305             if (mAutoTriggered) {
306                 // successful auto periodic synchrnization with source
307                 log("auto triggered assist");
308                 mAutoTriggered = false;
309                 // perform PAST with this device
310                 BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle);
311                 if (srcDevice != null) {
312                     BluetoothLeBroadcastReceiveState recvState =
313                             getBroadcastReceiveStateForSourceDevice(srcDevice);
314                     processPASyncState(recvState);
315                 } else {
316                     Log.w(TAG, "Autoassist: no matching device");
317                 }
318             }
319         } else {
320             Log.e(TAG, "Seems BASE is not in parsable format");
321             if (!mAutoTriggered) {
322                 BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle);
323                 cancelActiveSync(srcDevice);
324             } else {
325                 mAutoTriggered = false;
326             }
327         }
328     }
329 
parseScanRecord(int syncHandle, ScanRecord record)330     void parseScanRecord(int syncHandle, ScanRecord record) {
331         log("parseScanRecord" + record);
332         BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle);
333         Map<ParcelUuid, byte[]> bmsAdvDataMap = record.getServiceData();
334         if (bmsAdvDataMap != null) {
335             for (Map.Entry<ParcelUuid, byte[]> entry : bmsAdvDataMap.entrySet()) {
336                 log("ParcelUUid = " + entry.getKey() + ", Value = "
337                         + Arrays.toString(entry.getValue()));
338             }
339         }
340         byte[] advData = record.getServiceData(BassConstants.BASIC_AUDIO_UUID);
341         if (advData != null) {
342             parseBaseData(mDevice, syncHandle, advData);
343         } else {
344             Log.e(TAG, "No service data in Scan record");
345             if (!mAutoTriggered) {
346                 cancelActiveSync(srcDevice);
347             } else {
348                 mAutoTriggered = false;
349             }
350         }
351     }
352 
checkAndParseBroadcastName(ScanRecord record)353     private String checkAndParseBroadcastName(ScanRecord record) {
354         log("checkAndParseBroadcastName");
355         byte[] rawBytes = record.getBytes();
356         List<TypeValueEntry> entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes);
357         if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) {
358             Log.e(TAG, "Invalid LTV entries in Scan record");
359             return null;
360         }
361 
362         String broadcastName = null;
363         for (TypeValueEntry entry : entries) {
364             // Only use the first value of each type
365             if (broadcastName == null && entry.getType() == BassConstants.BCAST_NAME_AD_TYPE) {
366                 byte[] bytes = entry.getValue();
367                 int len = bytes.length;
368                 if (len < BassConstants.BCAST_NAME_LEN_MIN
369                         || len > BassConstants.BCAST_NAME_LEN_MAX) {
370                     Log.e(TAG, "Invalid broadcast name length in Scan record" + len);
371                     return null;
372                 }
373                 broadcastName = new String(bytes, StandardCharsets.UTF_8);
374             }
375         }
376         return broadcastName;
377     }
378 
selectSource( ScanResult scanRes, boolean autoTriggered)379     private boolean selectSource(
380             ScanResult scanRes, boolean autoTriggered) {
381         log("selectSource: ScanResult " + scanRes);
382         mAutoTriggered = autoTriggered;
383         mPASyncRetryCounter = 1;
384         // Cache Scan res for Retrys
385         mScanRes = scanRes;
386         /*This is an override case if Previous sync is still active, cancel It, but don't stop the
387          * Scan offload as we still trying to assist remote
388          */
389         mNoStopScanOffload = true;
390         cancelActiveSync(null);
391         try {
392             BluetoothMethodProxy.getInstance().periodicAdvertisingManagerRegisterSync(
393                     mPeriodicAdvManager, scanRes, 0, BassConstants.PSYNC_TIMEOUT,
394                     mPeriodicAdvCallback, null);
395         } catch (IllegalArgumentException ex) {
396             Log.w(TAG, "registerSync:IllegalArgumentException");
397             Message message = obtainMessage(STOP_SCAN_OFFLOAD);
398             sendMessage(message);
399             return false;
400         }
401         // updating mainly for Address type and PA Interval here
402         // extract BroadcastId from ScanResult
403         ScanRecord scanRecord = scanRes.getScanRecord();
404         if (scanRecord != null) {
405             Map<ParcelUuid, byte[]> listOfUuids = scanRecord.getServiceData();
406             int broadcastId = BassConstants.INVALID_BROADCAST_ID;
407             PublicBroadcastData pbData = null;
408             if (listOfUuids != null) {
409                 if (listOfUuids.containsKey(BassConstants.BAAS_UUID)) {
410                     byte[] bId = listOfUuids.get(BassConstants.BAAS_UUID);
411                     broadcastId = BassUtils.parseBroadcastId(bId);
412                 }
413                 if (listOfUuids.containsKey(BassConstants.PUBLIC_BROADCAST_UUID)) {
414                     byte[] pbAnnouncement =
415                             listOfUuids.get(BassConstants.PUBLIC_BROADCAST_UUID);
416                     pbData = PublicBroadcastData.parsePublicBroadcastData(pbAnnouncement);
417                 }
418             }
419             // Check if broadcast name present in scan record and parse
420             // null if no name present
421             String broadcastName = checkAndParseBroadcastName(scanRecord);
422 
423             mService.updatePeriodicAdvertisementResultMap(
424                     scanRes.getDevice(),
425                     scanRes.getDevice().getAddressType(),
426                     BassConstants.INVALID_SYNC_HANDLE,
427                     BassConstants.INVALID_ADV_SID,
428                     scanRes.getPeriodicAdvertisingInterval(),
429                     broadcastId,
430                     pbData,
431                     broadcastName);
432         }
433         return true;
434     }
435 
cancelActiveSync(BluetoothDevice sourceDev)436     private void cancelActiveSync(BluetoothDevice sourceDev) {
437         log("cancelActiveSync: sourceDev = " + sourceDev);
438         BluetoothDevice activeSyncedSrc = mService.getActiveSyncedSource(mDevice);
439 
440         /* Stop sync if there is some running */
441         if (activeSyncedSrc != null && (sourceDev == null || activeSyncedSrc.equals(sourceDev))) {
442             removeMessages(PSYNC_ACTIVE_TIMEOUT);
443             try {
444                 log("calling unregisterSync");
445                 mPeriodicAdvManager.unregisterSync(mPeriodicAdvCallback);
446             } catch (IllegalArgumentException ex) {
447                 Log.w(TAG, "unregisterSync:IllegalArgumentException");
448             }
449             mService.clearNotifiedFlags();
450             mService.setActiveSyncedSource(mDevice, null);
451             if (!mNoStopScanOffload) {
452                 // trigger scan stop here
453                 Message message = obtainMessage(STOP_SCAN_OFFLOAD);
454                 sendMessage(message);
455             }
456         }
457         mNoStopScanOffload = false;
458     }
459 
getBroadcastMetadataFromBaseData(BaseData baseData, BluetoothDevice device)460     private BluetoothLeBroadcastMetadata getBroadcastMetadataFromBaseData(BaseData baseData,
461             BluetoothDevice device) {
462         return getBroadcastMetadataFromBaseData(baseData, device, false);
463     }
464 
getBroadcastMetadataFromBaseData(BaseData baseData, BluetoothDevice device, boolean encrypted)465     private BluetoothLeBroadcastMetadata getBroadcastMetadataFromBaseData(BaseData baseData,
466             BluetoothDevice device, boolean encrypted) {
467         BluetoothLeBroadcastMetadata.Builder metaData =
468                 new BluetoothLeBroadcastMetadata.Builder();
469         int index = 0;
470         for (BaseData.BaseInformation baseLevel2 : baseData.getLevelTwo()) {
471             BluetoothLeBroadcastSubgroup.Builder subGroup =
472                     new BluetoothLeBroadcastSubgroup.Builder();
473             for (int j = 0; j < baseLevel2.numSubGroups; j ++) {
474                 BaseData.BaseInformation baseLevel3 =
475                         baseData.getLevelThree().get(index++);
476                 BluetoothLeBroadcastChannel.Builder channel =
477                         new BluetoothLeBroadcastChannel.Builder();
478                 channel.setChannelIndex(baseLevel3.index);
479                 channel.setCodecMetadata(BluetoothLeAudioCodecConfigMetadata.
480                         fromRawBytes(baseLevel3.codecConfigInfo));
481                 channel.setSelected(false);
482                 subGroup.addChannel(channel.build());
483             }
484             byte[] arrayCodecId = baseLevel2.codecId;
485             long codeId = ((long) (arrayCodecId[4] & 0xff)) << 32
486                     | (arrayCodecId[3] & 0xff) << 24
487                     | (arrayCodecId[2] & 0xff) << 16
488                     | (arrayCodecId[1] & 0xff) << 8
489                     | (arrayCodecId[0] & 0xff);
490             subGroup.setCodecId(codeId);
491             subGroup.setCodecSpecificConfig(BluetoothLeAudioCodecConfigMetadata.
492                     fromRawBytes(baseLevel2.codecConfigInfo));
493             subGroup.setContentMetadata(BluetoothLeAudioContentMetadata.
494                     fromRawBytes(baseLevel2.metaData));
495             metaData.addSubgroup(subGroup.build());
496         }
497         metaData.setSourceDevice(device, device.getAddressType());
498         byte[] arrayPresentationDelay = baseData.getLevelOne().presentationDelay;
499         int presentationDelay = (int) ((arrayPresentationDelay[2] & 0xff) << 16
500                 | (arrayPresentationDelay[1] & 0xff)
501                 | (arrayPresentationDelay[0] & 0xff));
502         metaData.setPresentationDelayMicros(presentationDelay);
503         PeriodicAdvertisementResult result =
504                 mService.getPeriodicAdvertisementResult(device);
505         if (result != null) {
506             int broadcastId = result.getBroadcastId();
507             log("broadcast ID: " + broadcastId);
508             metaData.setBroadcastId(broadcastId);
509             metaData.setSourceAdvertisingSid(result.getAdvSid());
510 
511             PublicBroadcastData pbData = result.getPublicBroadcastData();
512             if (pbData != null) {
513                 metaData.setPublicBroadcast(true);
514                 metaData.setAudioConfigQuality(pbData.getAudioConfigQuality());
515                 metaData.setPublicBroadcastMetadata(BluetoothLeAudioContentMetadata
516                         .fromRawBytes(pbData.getMetadata()));
517             }
518 
519             String broadcastName = result.getBroadcastName();
520             if (broadcastName != null) {
521                 metaData.setBroadcastName(broadcastName);
522             }
523         }
524         metaData.setEncrypted(encrypted);
525         return metaData.build();
526     }
527 
528     /** Internal periodc Advertising manager callback */
529     private PeriodicAdvertisingCallback mPeriodicAdvCallback =
530             new PeriodicAdvertisingCallback() {
531                 @Override
532                 public void onSyncEstablished(
533                         int syncHandle,
534                         BluetoothDevice device,
535                         int advertisingSid,
536                         int skip,
537                         int timeout,
538                         int status) {
539                     log("onSyncEstablished syncHandle: " + syncHandle
540                             + ", device: " + device
541                             + ", advertisingSid: " + advertisingSid
542                             + ", skip: " + skip
543                             + ", timeout: " + timeout
544                             + ", status: " + status);
545                     if (status == BluetoothGatt.GATT_SUCCESS) {
546                         // updates syncHandle, advSid
547                         // set other fields as invalid or null
548                         mService.updatePeriodicAdvertisementResultMap(
549                                 device,
550                                 BassConstants.INVALID_ADV_ADDRESS_TYPE,
551                                 syncHandle,
552                                 advertisingSid,
553                                 BassConstants.INVALID_ADV_INTERVAL,
554                                 BassConstants.INVALID_BROADCAST_ID,
555                                 null,
556                                 null);
557                         sendMessageDelayed(PSYNC_ACTIVE_TIMEOUT,
558                                 BassConstants.PSYNC_ACTIVE_TIMEOUT_MS);
559                         mService.setActiveSyncedSource(mDevice, device);
560                         mFirstTimeBisDiscoveryMap.put(syncHandle, true);
561                     } else {
562                         log("failed to sync to PA: " + mPASyncRetryCounter);
563                         mScanRes = null;
564                         if (!mAutoTriggered) {
565                             Message message = obtainMessage(STOP_SCAN_OFFLOAD);
566                             sendMessage(message);
567                         }
568                         mAutoTriggered = false;
569                     }
570                 }
571 
572                 @Override
573                 public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {
574                     log("onPeriodicAdvertisingReport");
575                     Boolean first = mFirstTimeBisDiscoveryMap.get(report.getSyncHandle());
576                     // Parse the BIS indices from report's service data
577                     if (first != null && first.booleanValue() == true) {
578                         parseScanRecord(report.getSyncHandle(), report.getData());
579                         mFirstTimeBisDiscoveryMap.put(report.getSyncHandle(), false);
580                     }
581                 }
582 
583                 @Override
584                 public void onSyncLost(int syncHandle) {
585                     log("OnSyncLost" + syncHandle);
586                     BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle);
587                     cancelActiveSync(srcDevice);
588                 }
589 
590                 @Override
591                 public void onBigInfoAdvertisingReport(int syncHandle, boolean encrypted) {
592                     log("onBIGInfoAdvertisingReport: syncHandle=" + syncHandle +
593                             " ,encrypted =" + encrypted);
594                     BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle);
595                     if (srcDevice == null) {
596                         log("No device found.");
597                         return;
598                     }
599                     PeriodicAdvertisementResult result =
600                             mService.getPeriodicAdvertisementResult(srcDevice);
601                     if (result == null) {
602                         log("No PA record found");
603                         return;
604                     }
605                     if (!result.isNotified()) {
606                         result.setNotified(true);
607                         BaseData baseData = mService.getBase(syncHandle);
608                         if (baseData == null) {
609                             log("No BaseData found");
610                             return;
611                         }
612                         BluetoothLeBroadcastMetadata metaData =
613                                 getBroadcastMetadataFromBaseData(baseData,
614                                         mService.getDeviceForSyncHandle(syncHandle), encrypted);
615                         log("Notify broadcast source found");
616                         mService.getCallbacks().notifySourceFound(metaData);
617                     }
618                 }
619             };
620 
broadcastReceiverState( BluetoothLeBroadcastReceiveState state, int sourceId)621     private void broadcastReceiverState(
622             BluetoothLeBroadcastReceiveState state, int sourceId) {
623         log("broadcastReceiverState: " + mDevice);
624         mService.getCallbacks().notifyReceiveStateChanged(mDevice, sourceId, state);
625     }
626 
627     @VisibleForTesting
isEmpty(final byte[] data)628     static boolean isEmpty(final byte[] data) {
629         return IntStream.range(0, data.length).parallel().allMatch(i -> data[i] == 0);
630     }
631 
processPASyncState(BluetoothLeBroadcastReceiveState recvState)632     private void processPASyncState(BluetoothLeBroadcastReceiveState recvState) {
633         log("processPASyncState " + recvState);
634         int serviceData = 0;
635         if (recvState == null) {
636             Log.e(TAG, "processPASyncState: recvState is null");
637             return;
638         }
639         int state = recvState.getPaSyncState();
640         if (state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) {
641             log("Initiate PAST procedure");
642             PeriodicAdvertisementResult result =
643                     mService.getPeriodicAdvertisementResult(
644                     recvState.getSourceDevice());
645             if (result != null) {
646                 int syncHandle = result.getSyncHandle();
647                 log("processPASyncState: syncHandle " + result.getSyncHandle());
648                 if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) {
649                     serviceData = 0x000000FF & recvState.getSourceId();
650                     serviceData = serviceData << 8;
651                     //advA matches EXT_ADV_ADDRESS
652                     //also matches source address (as we would have written)
653                     serviceData = serviceData
654                             & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS);
655                     serviceData = serviceData
656                             & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS);
657                     log("Initiate PAST for: " + mDevice + ", syncHandle: " +  syncHandle
658                             + "serviceData" + serviceData);
659                     BluetoothMethodProxy.getInstance().periodicAdvertisingManagerTransferSync(
660                             mPeriodicAdvManager, mDevice, serviceData, syncHandle);
661                 }
662             } else {
663                 if (mService.isLocalBroadcast(mPendingMetadata)) {
664                     int advHandle = mPendingMetadata.getSourceAdvertisingSid();
665                     serviceData = 0x000000FF & recvState.getSourceId();
666                     serviceData = serviceData << 8;
667                     // Address we set in the Source Address can differ from the address in the air
668                     serviceData = serviceData
669                             | BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS;
670                     log("Initiate local broadcast PAST for: " + mDevice
671                             + ", advSID/Handle: " +  advHandle
672                             + ", serviceData: " + serviceData);
673                     BluetoothMethodProxy.getInstance().periodicAdvertisingManagerTransferSetInfo(
674                             mPeriodicAdvManager, mDevice, serviceData, advHandle,
675                             mPeriodicAdvCallback);
676                 } else {
677                     Log.e(TAG, "There is no valid sync handle for this Source");
678                 }
679             }
680         } else if (state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
681                 || state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_NO_PAST) {
682             Message message = obtainMessage(STOP_SCAN_OFFLOAD);
683             sendMessage(message);
684         }
685     }
686 
checkAndUpdateBroadcastCode(BluetoothLeBroadcastReceiveState recvState)687     private void checkAndUpdateBroadcastCode(BluetoothLeBroadcastReceiveState recvState) {
688         log("checkAndUpdateBroadcastCode");
689         // non colocated case, Broadcast PIN should have been updated from lyaer
690         // If there is pending one process it Now
691         if (recvState.getBigEncryptionState()
692                 == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED
693                 && mSetBroadcastCodePending) {
694             log("Update the Broadcast now");
695             if (mSetBroadcastPINMetadata != null) {
696                 setCurrentBroadcastMetadata(recvState.getSourceId(),
697                         mSetBroadcastPINMetadata);
698             }
699             Message m = obtainMessage(BassClientStateMachine.SET_BCAST_CODE);
700             m.obj = recvState;
701             m.arg1 = ARGTYPE_RCVSTATE;
702             sendMessage(m);
703             mSetBroadcastCodePending = false;
704             mSetBroadcastPINMetadata = null;
705         }
706     }
707 
parseBroadcastReceiverState( byte[] receiverState)708     private BluetoothLeBroadcastReceiveState parseBroadcastReceiverState(
709             byte[] receiverState) {
710         byte sourceId = 0;
711         if (receiverState.length > 0) {
712             sourceId = receiverState[BassConstants.BCAST_RCVR_STATE_SRC_ID_IDX];
713         }
714         log("processBroadcastReceiverState: receiverState length: " + receiverState.length);
715 
716         BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
717         BluetoothLeBroadcastReceiveState recvState = null;
718         if (receiverState.length == 0
719                 || isEmpty(Arrays.copyOfRange(receiverState, 1, receiverState.length - 1))) {
720             String emptyBluetoothDevice = "00:00:00:00:00:00";
721             if (mPendingOperation == REMOVE_BCAST_SOURCE) {
722                 recvState = new BluetoothLeBroadcastReceiveState(mPendingSourceId,
723                         BluetoothDevice.ADDRESS_TYPE_PUBLIC, // sourceAddressType
724                         btAdapter.getRemoteDevice(emptyBluetoothDevice),  // sourceDevice
725                         0,  // sourceAdvertisingSid
726                         0,  // broadcastId
727                         BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, // paSyncState
728                         // bigEncryptionState
729                         BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
730                         null,   // badCode
731                         0,  // numSubgroups
732                         Arrays.asList(new Long[0]),   // bisSyncState
733                         Arrays.asList(new BluetoothLeAudioContentMetadata[0])    // subgroupMetadata
734                 );
735             } else if (receiverState.length == 0) {
736                 if (mBluetoothLeBroadcastReceiveStates != null) {
737                     mNextSourceId = (byte) mBluetoothLeBroadcastReceiveStates.size();
738                 }
739                 if (mNextSourceId >= mNumOfBroadcastReceiverStates) {
740                     Log.e(TAG, "reached the remote supported max SourceInfos");
741                     return null;
742                 }
743                 mNextSourceId++;
744                 recvState = new BluetoothLeBroadcastReceiveState(mNextSourceId,
745                         BluetoothDevice.ADDRESS_TYPE_PUBLIC, // sourceAddressType
746                         btAdapter.getRemoteDevice(emptyBluetoothDevice),   // sourceDevice
747                         0,  // sourceAdvertisingSid
748                         0,  // broadcastId
749                         BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, // paSyncState
750                         // bigEncryptionState
751                         BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
752                         null,   // badCode
753                         0,  // numSubgroups
754                         Arrays.asList(new Long[0]),   // bisSyncState
755                         Arrays.asList(new BluetoothLeAudioContentMetadata[0])    // subgroupMetadata
756                 );
757             }
758         } else {
759             byte metaDataSyncState = receiverState[BassConstants.BCAST_RCVR_STATE_PA_SYNC_IDX];
760             byte encryptionStatus = receiverState[BassConstants.BCAST_RCVR_STATE_ENC_STATUS_IDX];
761             byte[] badBroadcastCode = null;
762             int badBroadcastCodeLen = 0;
763             if (encryptionStatus
764                     == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE) {
765                 badBroadcastCode = new byte[BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE];
766                 System.arraycopy(
767                         receiverState,
768                         BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX,
769                         badBroadcastCode,
770                         0,
771                         BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE);
772                 badBroadcastCodeLen = BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE;
773             }
774             byte numSubGroups = receiverState[BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX
775                     + badBroadcastCodeLen];
776             int offset = BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX
777                     + badBroadcastCodeLen + 1;
778             ArrayList<BluetoothLeAudioContentMetadata> metadataList =
779                     new ArrayList<BluetoothLeAudioContentMetadata>();
780             ArrayList<Long> audioSyncState = new ArrayList<Long>();
781             for (int i = 0; i < numSubGroups; i++) {
782                 byte[] audioSyncIndex = new byte[BassConstants.BCAST_RCVR_STATE_BIS_SYNC_SIZE];
783                 System.arraycopy(receiverState, offset, audioSyncIndex, 0,
784                         BassConstants.BCAST_RCVR_STATE_BIS_SYNC_SIZE);
785                 offset += BassConstants.BCAST_RCVR_STATE_BIS_SYNC_SIZE;
786                 audioSyncState.add((long) Utils.byteArrayToInt(audioSyncIndex));
787 
788                 byte metaDataLength = receiverState[offset++];
789                 if (metaDataLength > 0) {
790                     log("metadata of length: " + metaDataLength + "is available");
791                     byte[] metaData = new byte[metaDataLength];
792                     System.arraycopy(receiverState, offset, metaData, 0, metaDataLength);
793                     offset += metaDataLength;
794                     metadataList.add(BluetoothLeAudioContentMetadata.fromRawBytes(metaData));
795                 } else {
796                     metadataList.add(BluetoothLeAudioContentMetadata.fromRawBytes(new byte[0]));
797                 }
798             }
799             byte[] broadcastIdBytes = new byte[mBroadcastSourceIdLength];
800             System.arraycopy(
801                     receiverState,
802                     BassConstants.BCAST_RCVR_STATE_SRC_BCAST_ID_START_IDX,
803                     broadcastIdBytes,
804                     0,
805                     mBroadcastSourceIdLength);
806             int broadcastId = BassUtils.parseBroadcastId(broadcastIdBytes);
807             byte[] sourceAddress = new byte[BassConstants.BCAST_RCVR_STATE_SRC_ADDR_SIZE];
808             System.arraycopy(
809                     receiverState,
810                     BassConstants.BCAST_RCVR_STATE_SRC_ADDR_START_IDX,
811                     sourceAddress,
812                     0,
813                     BassConstants.BCAST_RCVR_STATE_SRC_ADDR_SIZE);
814             byte sourceAddressType = receiverState[BassConstants
815                     .BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX];
816             BassUtils.reverse(sourceAddress);
817             String address = Utils.getAddressStringFromByte(sourceAddress);
818             BluetoothDevice device = btAdapter.getRemoteLeDevice(
819                     address, sourceAddressType);
820             byte sourceAdvSid = receiverState[BassConstants.BCAST_RCVR_STATE_SRC_ADV_SID_IDX];
821             recvState = new BluetoothLeBroadcastReceiveState(
822                     sourceId,
823                     (int) sourceAddressType,
824                     device,
825                     sourceAdvSid,
826                     broadcastId,
827                     (int) metaDataSyncState,
828                     (int) encryptionStatus,
829                     badBroadcastCode,
830                     numSubGroups,
831                     audioSyncState,
832                     metadataList);
833             log("Receiver state: "
834                     + "\n\tSource ID: " + sourceId
835                     + "\n\tSource Address Type: " + (int) sourceAddressType
836                     + "\n\tDevice: " + device
837                     + "\n\tSource Adv SID: " + sourceAdvSid
838                     + "\n\tBroadcast ID: " + broadcastId
839                     + "\n\tMetadata Sync State: " + (int) metaDataSyncState
840                     + "\n\tEncryption Status: " + (int) encryptionStatus
841                     + "\n\tBad Broadcast Code: " + Arrays.toString(badBroadcastCode)
842                     + "\n\tNumber Of Subgroups: " + numSubGroups
843                     + "\n\tAudio Sync State: " + audioSyncState
844                     + "\n\tMetadata: " + metadataList);
845         }
846         return recvState;
847     }
848 
processBroadcastReceiverState( byte[] receiverState, BluetoothGattCharacteristic characteristic)849     private void processBroadcastReceiverState(
850             byte[] receiverState, BluetoothGattCharacteristic characteristic) {
851         log("processBroadcastReceiverState: characteristic:" + characteristic);
852         BluetoothLeBroadcastReceiveState recvState = parseBroadcastReceiverState(
853                 receiverState);
854         if (recvState == null) {
855             log("processBroadcastReceiverState: Null recvState");
856             return;
857         } else if (recvState.getSourceId() == -1) {
858             log("processBroadcastReceiverState: invalid index: " + recvState.getSourceId());
859             return;
860         }
861         BluetoothLeBroadcastReceiveState oldRecvState =
862                 mBluetoothLeBroadcastReceiveStates.get(characteristic.getInstanceId());
863         if (oldRecvState == null) {
864             log("Initial Read and Populating values");
865             if (mBluetoothLeBroadcastReceiveStates.size() == mNumOfBroadcastReceiverStates) {
866                 Log.e(TAG, "reached the Max SourceInfos");
867                 return;
868             }
869             mBluetoothLeBroadcastReceiveStates.put(characteristic.getInstanceId(), recvState);
870             checkAndUpdateBroadcastCode(recvState);
871             processPASyncState(recvState);
872         } else {
873             log("old sourceInfo: " + oldRecvState);
874             log("new sourceInfo: " + recvState);
875             mBluetoothLeBroadcastReceiveStates.replace(characteristic.getInstanceId(), recvState);
876             String emptyBluetoothDevice = "00:00:00:00:00:00";
877             if (oldRecvState.getSourceDevice() == null
878                     || oldRecvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) {
879                 log("New Source Addition");
880                 mService.getCallbacks().notifySourceAdded(mDevice, recvState,
881                         BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
882                 if (mPendingMetadata != null) {
883                     setCurrentBroadcastMetadata(recvState.getSourceId(), mPendingMetadata);
884                 }
885                 checkAndUpdateBroadcastCode(recvState);
886                 processPASyncState(recvState);
887             } else {
888                 if (recvState.getSourceDevice() == null
889                         || recvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) {
890                     BluetoothDevice removedDevice = oldRecvState.getSourceDevice();
891                     log("sourceInfo removal" + removedDevice);
892                     cancelActiveSync(removedDevice);
893                     setCurrentBroadcastMetadata(oldRecvState.getSourceId(), null);
894                     mService.getCallbacks().notifySourceRemoved(mDevice,
895                             oldRecvState.getSourceId(),
896                             BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
897                 } else {
898                     log("update to an existing recvState");
899                     setCurrentBroadcastMetadata(recvState.getSourceId(), mPendingMetadata);
900                     mService.getCallbacks().notifySourceModified(mDevice,
901                             recvState.getSourceId(), BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
902                     checkAndUpdateBroadcastCode(recvState);
903                     processPASyncState(recvState);
904 
905                     if (isPendingRemove(recvState.getSourceId())) {
906                         Message message = obtainMessage(REMOVE_BCAST_SOURCE);
907                         message.arg1 = recvState.getSourceId();
908                         sendMessage(message);
909                     }
910                 }
911             }
912         }
913         broadcastReceiverState(recvState, recvState.getSourceId());
914     }
915 
916     // Implements callback methods for GATT events that the app cares about.
917     // For example, connection change and services discovered.
918     final class GattCallback extends BluetoothGattCallback {
919         @Override
onConnectionStateChange(BluetoothGatt gatt, int status, int newState)920         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
921             boolean isStateChanged = false;
922             log("onConnectionStateChange : Status=" + status + "newState" + newState);
923             if (newState == BluetoothProfile.STATE_CONNECTED
924                     && getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
925                 isStateChanged = true;
926                 Log.w(TAG, "Bassclient Connected from Disconnected state: " + mDevice);
927                 if (mService.okToConnect(mDevice)) {
928                     log("Bassclient Connected to: " + mDevice);
929                     if (mBluetoothGatt != null) {
930                         log("Attempting to start service discovery:"
931                                 + mBluetoothGatt.discoverServices());
932                         mDiscoveryInitiated = true;
933                     }
934                 } else if (mBluetoothGatt != null) {
935                     // Reject the connection
936                     Log.w(TAG, "Bassclient Connect request rejected: " + mDevice);
937                     mBluetoothGatt.disconnect();
938                     mBluetoothGatt.close();
939                     mBluetoothGatt = null;
940                     // force move to disconnected
941                     newState = BluetoothProfile.STATE_DISCONNECTED;
942                 }
943             } else if (newState == BluetoothProfile.STATE_DISCONNECTED
944                     && getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
945                 isStateChanged = true;
946                 log("Disconnected from Bass GATT server.");
947             }
948             if (isStateChanged) {
949                 Message m = obtainMessage(CONNECTION_STATE_CHANGED);
950                 m.obj = newState;
951                 sendMessage(m);
952             }
953         }
954 
955         @Override
onServicesDiscovered(BluetoothGatt gatt, int status)956         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
957             log("onServicesDiscovered:" + status);
958             if (mDiscoveryInitiated) {
959                 mDiscoveryInitiated = false;
960                 if (status == BluetoothGatt.GATT_SUCCESS && mBluetoothGatt != null) {
961                     mBluetoothGatt.requestMtu(BassConstants.BASS_MAX_BYTES);
962                     mMTUChangeRequested = true;
963                 } else {
964                     Log.w(TAG, "onServicesDiscovered received: "
965                             + status + "mBluetoothGatt" + mBluetoothGatt);
966                 }
967             } else {
968                 log("remote initiated callback");
969             }
970         }
971 
972         @Override
onCharacteristicRead( BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)973         public void onCharacteristicRead(
974                 BluetoothGatt gatt,
975                 BluetoothGattCharacteristic characteristic,
976                 int status) {
977             log("onCharacteristicRead:: status: " + status + "char:" + characteristic);
978             if (status == BluetoothGatt.GATT_SUCCESS && characteristic.getUuid()
979                     .equals(BassConstants.BASS_BCAST_RECEIVER_STATE)) {
980                 log("onCharacteristicRead: BASS_BCAST_RECEIVER_STATE: status" + status);
981                 if (characteristic.getValue() == null) {
982                     Log.e(TAG, "Remote receiver state is NULL");
983                     return;
984                 }
985                 logByteArray("Received ", characteristic.getValue(), 0,
986                         characteristic.getValue().length);
987                 processBroadcastReceiverState(characteristic.getValue(), characteristic);
988             }
989             // switch to receiving notifications after initial characteristic read
990             BluetoothGattDescriptor desc = characteristic
991                     .getDescriptor(BassConstants.CLIENT_CHARACTERISTIC_CONFIG);
992             if (mBluetoothGatt != null && desc != null) {
993                 log("Setting the value for Desc");
994                 mBluetoothGatt.setCharacteristicNotification(characteristic, true);
995                 desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
996                 mBluetoothGatt.writeDescriptor(desc);
997             } else {
998                 Log.w(TAG, "CCC for " + characteristic + "seem to be not present");
999                 // at least move the SM to stable state
1000                 Message m = obtainMessage(GATT_TXN_PROCESSED);
1001                 m.arg1 = status;
1002                 sendMessage(m);
1003             }
1004         }
1005 
1006         @Override
onDescriptorWrite( BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status)1007         public void onDescriptorWrite(
1008                 BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
1009             log("onDescriptorWrite");
1010             if (status == BluetoothGatt.GATT_SUCCESS
1011                     && descriptor.getUuid()
1012                     .equals(BassConstants.CLIENT_CHARACTERISTIC_CONFIG)) {
1013                 log("CCC write resp");
1014             }
1015 
1016             // Move the SM to connected so further reads happens
1017             Message m = obtainMessage(GATT_TXN_PROCESSED);
1018             m.arg1 = status;
1019             sendMessage(m);
1020         }
1021 
1022         @Override
onMtuChanged(BluetoothGatt gatt, int mtu, int status)1023         public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
1024             log("onMtuChanged: mtu:" + mtu);
1025             if (mMTUChangeRequested && mBluetoothGatt != null) {
1026                 acquireAllBassChars();
1027                 mMTUChangeRequested = false;
1028             } else {
1029                 log("onMtuChanged is remote initiated trigger, mBluetoothGatt:"
1030                         + mBluetoothGatt);
1031             }
1032         }
1033 
1034         @Override
onCharacteristicChanged( BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)1035         public void onCharacteristicChanged(
1036                 BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
1037             log("onCharacteristicChanged :: " + characteristic.getUuid().toString());
1038             if (characteristic.getUuid().equals(BassConstants.BASS_BCAST_RECEIVER_STATE)) {
1039                 log("onCharacteristicChanged is rcvr State :: "
1040                         + characteristic.getUuid().toString());
1041                 if (characteristic.getValue() == null) {
1042                     Log.e(TAG, "Remote receiver state is NULL");
1043                     return;
1044                 }
1045                 logByteArray("onCharacteristicChanged: Received ",
1046                         characteristic.getValue(),
1047                         0,
1048                         characteristic.getValue().length);
1049                 processBroadcastReceiverState(characteristic.getValue(), characteristic);
1050             }
1051         }
1052 
1053         @Override
onCharacteristicWrite( BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)1054         public void onCharacteristicWrite(
1055                 BluetoothGatt gatt,
1056                 BluetoothGattCharacteristic characteristic,
1057                 int status) {
1058             log("onCharacteristicWrite: " + characteristic.getUuid().toString()
1059                     + "status:" + status);
1060             if (status == 0
1061                     && characteristic.getUuid()
1062                     .equals(BassConstants.BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) {
1063                 log("BASS_BCAST_AUDIO_SCAN_CTRL_POINT is written successfully");
1064             }
1065             Message m = obtainMessage(GATT_TXN_PROCESSED);
1066             m.arg1 = status;
1067             sendMessage(m);
1068         }
1069     }
1070 
1071     /**
1072      * Connects to the GATT server of the device.
1073      *
1074      * @return {@code true} if it successfully connects to the GATT server.
1075      */
1076     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
connectGatt(Boolean autoConnect)1077     public boolean connectGatt(Boolean autoConnect) {
1078         if (mGattCallback == null) {
1079             mGattCallback = new GattCallback();
1080         }
1081 
1082         BluetoothGatt gatt = mDevice.connectGatt(mService, autoConnect,
1083                 mGattCallback, BluetoothDevice.TRANSPORT_LE,
1084                 (BluetoothDevice.PHY_LE_1M_MASK
1085                         | BluetoothDevice.PHY_LE_2M_MASK
1086                         | BluetoothDevice.PHY_LE_CODED_MASK), null);
1087 
1088         if (gatt != null) {
1089             mBluetoothGatt = new BluetoothGattTestableWrapper(gatt);
1090         }
1091 
1092         return mBluetoothGatt != null;
1093     }
1094 
1095     /**
1096      * getAllSources
1097      */
getAllSources()1098     public List<BluetoothLeBroadcastReceiveState> getAllSources() {
1099         log("getAllSources");
1100         List list = new ArrayList(mBluetoothLeBroadcastReceiveStates.values());
1101         return list;
1102     }
1103 
acquireAllBassChars()1104     void acquireAllBassChars() {
1105         clearCharsCache();
1106         BluetoothGattService service = null;
1107         if (mBluetoothGatt != null) {
1108             log("getting Bass Service handle");
1109             service = mBluetoothGatt.getService(BassConstants.BASS_UUID);
1110         }
1111         if (service == null) {
1112             log("acquireAllBassChars: BASS service not found");
1113             return;
1114         }
1115         log("found BASS_SERVICE");
1116         List<BluetoothGattCharacteristic> allChars = service.getCharacteristics();
1117         int numOfChars = allChars.size();
1118         mNumOfBroadcastReceiverStates = numOfChars - 1;
1119         log("Total number of chars" + numOfChars);
1120         for (int i = 0; i < allChars.size(); i++) {
1121             if (allChars.get(i).getUuid().equals(BassConstants.BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) {
1122                 mBroadcastScanControlPoint = allChars.get(i);
1123                 log("Index of ScanCtrlPoint:" + i);
1124             } else {
1125                 log("Reading " + i + "th ReceiverState");
1126                 mBroadcastCharacteristics.add(allChars.get(i));
1127                 Message m = obtainMessage(READ_BASS_CHARACTERISTICS);
1128                 m.obj = allChars.get(i);
1129                 sendMessage(m);
1130             }
1131         }
1132     }
1133 
clearCharsCache()1134     void clearCharsCache() {
1135         if (mBroadcastCharacteristics != null) {
1136             mBroadcastCharacteristics.clear();
1137         }
1138         if (mBroadcastScanControlPoint != null) {
1139             mBroadcastScanControlPoint = null;
1140         }
1141         mNumOfBroadcastReceiverStates = 0;
1142         if (mBluetoothLeBroadcastReceiveStates != null) {
1143             mBluetoothLeBroadcastReceiveStates.clear();
1144         }
1145         mPendingOperation = -1;
1146         mPendingMetadata = null;
1147         mCurrentMetadata.clear();
1148         mPendingRemove.clear();
1149     }
1150 
1151     @VisibleForTesting
1152     class Disconnected extends State {
1153         @Override
enter()1154         public void enter() {
1155             log("Enter Disconnected(" + mDevice + "): "
1156                     + messageWhatToString(getCurrentMessage().what));
1157             clearCharsCache();
1158             mNextSourceId = 0;
1159             removeDeferredMessages(DISCONNECT);
1160             if (mLastConnectionState == -1) {
1161                 log("no Broadcast of initial profile state ");
1162             } else {
1163                 broadcastConnectionState(
1164                         mDevice, mLastConnectionState, BluetoothProfile.STATE_DISCONNECTED);
1165                 if (mLastConnectionState != BluetoothProfile.STATE_DISCONNECTED) {
1166                     // Reconnect in background if not disallowed by the service
1167                     if (mService.okToConnect(mDevice) && mAllowReconnect) {
1168                         connectGatt(false);
1169                     }
1170                 }
1171             }
1172         }
1173 
1174         @Override
exit()1175         public void exit() {
1176             log("Exit Disconnected(" + mDevice + "): "
1177                     + messageWhatToString(getCurrentMessage().what));
1178             mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
1179         }
1180 
1181         @Override
processMessage(Message message)1182         public boolean processMessage(Message message) {
1183             log("Disconnected process message(" + mDevice
1184                     + "): " + messageWhatToString(message.what));
1185             switch (message.what) {
1186                 case CONNECT:
1187                     log("Connecting to " + mDevice);
1188                     if (mBluetoothGatt != null) {
1189                         Log.d(TAG, "clear off, pending wl connection");
1190                         mBluetoothGatt.disconnect();
1191                         mBluetoothGatt.close();
1192                         mBluetoothGatt = null;
1193                     }
1194                     mAllowReconnect = true;
1195                     if (connectGatt(mIsAllowedList)) {
1196                         transitionTo(mConnecting);
1197                     } else {
1198                         Log.e(TAG, "Disconnected: error connecting to " + mDevice);
1199                     }
1200                     break;
1201                 case DISCONNECT:
1202                     // Disconnect if there's an ongoing background connection
1203                     mAllowReconnect = false;
1204                     if (mBluetoothGatt != null) {
1205                         log("Cancelling the background connection to " + mDevice);
1206                         mBluetoothGatt.disconnect();
1207                         mBluetoothGatt.close();
1208                         mBluetoothGatt = null;
1209                     } else {
1210                         Log.d(TAG, "Disconnected: DISCONNECT ignored: " + mDevice);
1211                     }
1212                     break;
1213                 case CONNECTION_STATE_CHANGED:
1214                     int state = (int) message.obj;
1215                     Log.w(TAG, "connection state changed:" + state);
1216                     if (state == BluetoothProfile.STATE_CONNECTED) {
1217                         log("remote/wl connection");
1218                         transitionTo(mConnected);
1219                     } else {
1220                         Log.w(TAG, "Disconnected: Connection failed to " + mDevice);
1221                     }
1222                     break;
1223                 case PSYNC_ACTIVE_TIMEOUT:
1224                     cancelActiveSync(null);
1225                     break;
1226                 default:
1227                     log("DISCONNECTED: not handled message:" + message.what);
1228                     return NOT_HANDLED;
1229             }
1230             return HANDLED;
1231         }
1232     }
1233 
1234     @VisibleForTesting
1235     class Connecting extends State {
1236         @Override
enter()1237         public void enter() {
1238             log("Enter Connecting(" + mDevice + "): "
1239                     + messageWhatToString(getCurrentMessage().what));
1240             sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs);
1241             broadcastConnectionState(
1242                     mDevice, mLastConnectionState, BluetoothProfile.STATE_CONNECTING);
1243         }
1244 
1245         @Override
exit()1246         public void exit() {
1247             log("Exit Connecting(" + mDevice + "): "
1248                     + messageWhatToString(getCurrentMessage().what));
1249             mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
1250             removeMessages(CONNECT_TIMEOUT);
1251         }
1252 
1253         @Override
processMessage(Message message)1254         public boolean processMessage(Message message) {
1255             log("Connecting process message(" + mDevice + "): "
1256                     + messageWhatToString(message.what));
1257             switch (message.what) {
1258                 case CONNECT:
1259                     log("Already Connecting to " + mDevice);
1260                     log("Ignore this connection request " + mDevice);
1261                     break;
1262                 case DISCONNECT:
1263                     Log.w(TAG, "Connecting: DISCONNECT deferred: " + mDevice);
1264                     deferMessage(message);
1265                     break;
1266                 case READ_BASS_CHARACTERISTICS:
1267                     Log.w(TAG, "defer READ_BASS_CHARACTERISTICS requested!: " + mDevice);
1268                     deferMessage(message);
1269                     break;
1270                 case CONNECTION_STATE_CHANGED:
1271                     int state = (int) message.obj;
1272                     Log.w(TAG, "Connecting: connection state changed:" + state);
1273                     if (state == BluetoothProfile.STATE_CONNECTED) {
1274                         transitionTo(mConnected);
1275                     } else {
1276                         Log.w(TAG, "Connection failed to " + mDevice);
1277                         transitionTo(mDisconnected);
1278                     }
1279                     break;
1280                 case CONNECT_TIMEOUT:
1281                     Log.w(TAG, "CONNECT_TIMEOUT");
1282                     BluetoothDevice device = (BluetoothDevice) message.obj;
1283                     if (!mDevice.equals(device)) {
1284                         Log.e(TAG, "Unknown device timeout " + device);
1285                         break;
1286                     }
1287                     transitionTo(mDisconnected);
1288                     break;
1289                 case PSYNC_ACTIVE_TIMEOUT:
1290                     deferMessage(message);
1291                     break;
1292                 default:
1293                     log("CONNECTING: not handled message:" + message.what);
1294                     return NOT_HANDLED;
1295             }
1296             return HANDLED;
1297         }
1298     }
1299 
getBisSyncFromChannelPreference( List<BluetoothLeBroadcastChannel> channels)1300     private static int getBisSyncFromChannelPreference(
1301                 List<BluetoothLeBroadcastChannel> channels) {
1302         int bisSync = 0;
1303         for (BluetoothLeBroadcastChannel channel : channels) {
1304             if (channel.isSelected()) {
1305                 if (channel.getChannelIndex() == 0) {
1306                     Log.e(TAG, "getBisSyncFromChannelPreference: invalid channel index=0");
1307                     continue;
1308                 }
1309                 bisSync |= 1 << (channel.getChannelIndex() - 1);
1310             }
1311         }
1312 
1313         return bisSync;
1314     }
1315 
convertMetadataToAddSourceByteArray(BluetoothLeBroadcastMetadata metaData)1316     private byte[] convertMetadataToAddSourceByteArray(BluetoothLeBroadcastMetadata metaData) {
1317         ByteArrayOutputStream stream = new ByteArrayOutputStream();
1318         BluetoothDevice advSource = metaData.getSourceDevice();
1319 
1320         // Opcode
1321         stream.write(OPCODE_ADD_SOURCE);
1322 
1323         // Advertiser_Address_Type
1324         stream.write(metaData.getSourceAddressType());
1325 
1326         // Advertiser_Address
1327         byte[] bcastSourceAddr = Utils.getBytesFromAddress(advSource.getAddress());
1328         BassUtils.reverse(bcastSourceAddr);
1329         stream.write(bcastSourceAddr, 0, 6);
1330         log("Address bytes: " + advSource);
1331 
1332         // Advertising_SID
1333         stream.write(metaData.getSourceAdvertisingSid());
1334 
1335         // Broadcast_ID
1336         stream.write(metaData.getBroadcastId() & 0x00000000000000FF);
1337         stream.write((metaData.getBroadcastId() & 0x000000000000FF00) >>> 8);
1338         stream.write((metaData.getBroadcastId() & 0x0000000000FF0000) >>> 16);
1339         log("mBroadcastId: " + metaData.getBroadcastId());
1340 
1341         // PA_Sync
1342         if (!mDefNoPAS) {
1343             stream.write(0x01);
1344         } else {
1345             log("setting PA sync to ZERO");
1346             stream.write(0x00);
1347         }
1348 
1349         // PA_Interval
1350         stream.write((metaData.getPaSyncInterval() & 0x00000000000000FF));
1351         stream.write((metaData.getPaSyncInterval() & 0x000000000000FF00) >>> 8);
1352 
1353         // Num_Subgroups
1354         List<BluetoothLeBroadcastSubgroup> subGroups = metaData.getSubgroups();
1355         stream.write(metaData.getSubgroups().size());
1356 
1357         for (BluetoothLeBroadcastSubgroup subGroup : subGroups) {
1358             // BIS_Sync
1359             int bisSync = getBisSyncFromChannelPreference(subGroup.getChannels());
1360             if (bisSync == 0) {
1361                 bisSync = 0xFFFFFFFF;
1362             }
1363             stream.write(bisSync & 0x00000000000000FF);
1364             stream.write((bisSync & 0x000000000000FF00) >>> 8);
1365             stream.write((bisSync & 0x0000000000FF0000) >>> 16);
1366             stream.write((bisSync & 0x00000000FF000000) >>> 24);
1367 
1368             // Metadata_Length
1369             BluetoothLeAudioContentMetadata metadata = subGroup.getContentMetadata();
1370             stream.write(metadata.getRawMetadata().length);
1371 
1372             // Metadata
1373             stream.write(metadata.getRawMetadata(), 0, metadata.getRawMetadata().length);
1374         }
1375 
1376         byte[] res = stream.toByteArray();
1377         log("ADD_BCAST_SOURCE in Bytes");
1378         BassUtils.printByteArray(res);
1379         return res;
1380     }
1381 
convertBroadcastMetadataToUpdateSourceByteArray(int sourceId, BluetoothLeBroadcastMetadata metaData, int paSync)1382     private byte[] convertBroadcastMetadataToUpdateSourceByteArray(int sourceId,
1383             BluetoothLeBroadcastMetadata metaData, int paSync) {
1384         BluetoothLeBroadcastReceiveState existingState =
1385                 getBroadcastReceiveStateForSourceId(sourceId);
1386         if (existingState == null) {
1387             log("no existing SI for update source op");
1388             return null;
1389         }
1390         BluetoothDevice broadcastSource = metaData.getSourceDevice();
1391         PeriodicAdvertisementResult paRes =
1392                 mService.getPeriodicAdvertisementResult(broadcastSource);
1393         if (paRes == null) {
1394             Log.e(TAG, "No matching psync, scan res for update");
1395             mService.getCallbacks().notifySourceRemoveFailed(
1396                     mDevice, sourceId, BluetoothStatusCodes.ERROR_UNKNOWN);
1397             return null;
1398         }
1399         // populate metadata from BASE levelOne
1400         BaseData base = mService.getBase(paRes.getSyncHandle());
1401         if (base == null) {
1402             Log.e(TAG, "No valid base data populated for this device");
1403             mService.getCallbacks().notifySourceRemoveFailed(
1404                     mDevice, sourceId, BluetoothStatusCodes.ERROR_UNKNOWN);
1405             return null;
1406         }
1407         byte numSubGroups = base.getNumberOfSubgroupsofBIG();
1408         byte[] res = new byte[UPDATE_SOURCE_FIXED_LENGTH + numSubGroups * 5];
1409         int offset = 0;
1410         // Opcode
1411         res[offset++] = OPCODE_UPDATE_SOURCE;
1412         // Source_ID
1413         res[offset++] = (byte) sourceId;
1414         // PA_Sync
1415         if (paSync != BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_INVALID) {
1416             res[offset++] = (byte) paSync;
1417         } else if (existingState.getPaSyncState()
1418                 == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) {
1419             res[offset++] = (byte) (0x01);
1420         } else {
1421             res[offset++] = (byte) 0x00;
1422         }
1423         // PA_Interval
1424         res[offset++] = (byte) 0xFF;
1425         res[offset++] = (byte) 0xFF;
1426         // Num_Subgroups
1427         res[offset++] = numSubGroups;
1428         for (int i = 0; i < numSubGroups; i++) {
1429             int bisIndexValue;
1430             if (paSync != BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_INVALID) {
1431                 bisIndexValue = 0;
1432             } else {
1433                 bisIndexValue = existingState.getBisSyncState().get(i).intValue();
1434             }
1435             log("UPDATE_BCAST_SOURCE: bisIndexValue : " + bisIndexValue);
1436             // BIS_Sync
1437             res[offset++] = (byte) (bisIndexValue & 0x00000000000000FF);
1438             res[offset++] = (byte) ((bisIndexValue & 0x000000000000FF00) >>> 8);
1439             res[offset++] = (byte) ((bisIndexValue & 0x0000000000FF0000) >>> 16);
1440             res[offset++] = (byte) ((bisIndexValue & 0x00000000FF000000) >>> 24);
1441             // Metadata_Length; On Modify source, don't update any Metadata
1442             res[offset++] = 0;
1443         }
1444         log("UPDATE_BCAST_SOURCE in Bytes");
1445         BassUtils.printByteArray(res);
1446         return res;
1447     }
1448 
convertRecvStateToSetBroadcastCodeByteArray( BluetoothLeBroadcastReceiveState recvState)1449     private byte[] convertRecvStateToSetBroadcastCodeByteArray(
1450             BluetoothLeBroadcastReceiveState recvState) {
1451         byte[] res = new byte[BassConstants.PIN_CODE_CMD_LEN];
1452         // Opcode
1453         res[0] = OPCODE_SET_BCAST_PIN;
1454         // Source_ID
1455         res[1] = (byte) recvState.getSourceId();
1456         log("convertRecvStateToSetBroadcastCodeByteArray: Source device : "
1457                 + recvState.getSourceDevice());
1458         BluetoothLeBroadcastMetadata metaData =
1459                 getCurrentBroadcastMetadata(recvState.getSourceId());
1460         if (metaData == null) {
1461             Log.e(TAG, "Fail to find broadcast source, sourceId = "
1462                     + recvState.getSourceId());
1463             return null;
1464         }
1465         // Broadcast Code
1466         byte[] actualPIN = metaData.getBroadcastCode();
1467         if (actualPIN == null) {
1468             Log.e(TAG, "actual PIN is null");
1469             return null;
1470         } else {
1471             log("byte array broadcast Code:" + Arrays.toString(actualPIN));
1472             log("pinLength:" + actualPIN.length);
1473             // Broadcast_Code, Fill the PIN code in the Last Position
1474             // This effectively adds padding zeros to MSB positions when the broadcast code
1475             // is shorter than 16 octets, skip the first 2 bytes for opcode and source_id.
1476             System.arraycopy(actualPIN, 0, res, 2, actualPIN.length);
1477             log("SET_BCAST_PIN in Bytes");
1478             BassUtils.printByteArray(res);
1479         }
1480         return res;
1481     }
1482 
isItRightTimeToUpdateBroadcastPin(byte sourceId)1483     private boolean isItRightTimeToUpdateBroadcastPin(byte sourceId) {
1484         Collection<BluetoothLeBroadcastReceiveState> recvStates =
1485                 mBluetoothLeBroadcastReceiveStates.values();
1486         Iterator<BluetoothLeBroadcastReceiveState> iterator = recvStates.iterator();
1487         boolean retval = false;
1488         if (mForceSB) {
1489             log("force SB is set");
1490             return true;
1491         }
1492         while (iterator.hasNext()) {
1493             BluetoothLeBroadcastReceiveState state = iterator.next();
1494             if (state == null) {
1495                 log("Source state is null");
1496                 continue;
1497             }
1498             if (sourceId == state.getSourceId() && state.getBigEncryptionState()
1499                     == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED) {
1500                 retval = true;
1501                 break;
1502             }
1503         }
1504         log("IsItRightTimeToUpdateBroadcastPIN returning:" + retval);
1505         return retval;
1506     }
1507 
1508     @VisibleForTesting
1509     class Connected extends State {
1510         @Override
enter()1511         public void enter() {
1512             log("Enter Connected(" + mDevice + "): "
1513                     + messageWhatToString(getCurrentMessage().what));
1514             removeDeferredMessages(CONNECT);
1515             if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) {
1516                 log("CONNECTED->CONNECTED: Ignore");
1517                 // Broadcast for testing purpose only
1518                 if (Utils.isInstrumentationTestMode()) {
1519                     Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST");
1520                     Utils.sendBroadcast(mService, intent, BLUETOOTH_CONNECT,
1521                             Utils.getTempAllowlistBroadcastOptions());
1522                 }
1523             } else {
1524                 broadcastConnectionState(mDevice, mLastConnectionState,
1525                         BluetoothProfile.STATE_CONNECTED);
1526             }
1527         }
1528 
1529         @Override
exit()1530         public void exit() {
1531             log("Exit Connected(" + mDevice + "): "
1532                     + messageWhatToString(getCurrentMessage().what));
1533             mLastConnectionState = BluetoothProfile.STATE_CONNECTED;
1534         }
1535 
1536         @Override
processMessage(Message message)1537         public boolean processMessage(Message message) {
1538             log("Connected process message(" + mDevice + "): " + messageWhatToString(message.what));
1539             BluetoothLeBroadcastMetadata metaData;
1540             switch (message.what) {
1541                 case CONNECT:
1542                     Log.w(TAG, "Connected: CONNECT ignored: " + mDevice);
1543                     break;
1544                 case DISCONNECT:
1545                     log("Disconnecting from " + mDevice);
1546                     mAllowReconnect = false;
1547                     if (mBluetoothGatt != null) {
1548                         mBluetoothGatt.disconnect();
1549                         mBluetoothGatt.close();
1550                         mBluetoothGatt = null;
1551                         cancelActiveSync(null);
1552                         transitionTo(mDisconnected);
1553                     } else {
1554                         log("mBluetoothGatt is null");
1555                     }
1556                     break;
1557                 case CONNECTION_STATE_CHANGED:
1558                     int state = (int) message.obj;
1559                     Log.w(TAG, "Connected:connection state changed:" + state);
1560                     if (state == BluetoothProfile.STATE_CONNECTED) {
1561                         Log.w(TAG, "device is already connected to Bass" + mDevice);
1562                     } else {
1563                         Log.w(TAG, "unexpected disconnected from " + mDevice);
1564                         cancelActiveSync(null);
1565                         transitionTo(mDisconnected);
1566                     }
1567                     break;
1568                 case READ_BASS_CHARACTERISTICS:
1569                     BluetoothGattCharacteristic characteristic =
1570                             (BluetoothGattCharacteristic) message.obj;
1571                     if (mBluetoothGatt != null) {
1572                         mBluetoothGatt.readCharacteristic(characteristic);
1573                         transitionTo(mConnectedProcessing);
1574                     } else {
1575                         Log.e(TAG, "READ_BASS_CHARACTERISTICS is ignored, Gatt handle is null");
1576                     }
1577                     break;
1578                 case START_SCAN_OFFLOAD:
1579                     if (mBluetoothGatt != null && mBroadcastScanControlPoint != null) {
1580                         mBroadcastScanControlPoint.setValue(REMOTE_SCAN_START);
1581                         mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint);
1582                         mPendingOperation = message.what;
1583                         transitionTo(mConnectedProcessing);
1584                     } else {
1585                         log("no Bluetooth Gatt handle, may need to fetch write");
1586                     }
1587                     break;
1588                 case STOP_SCAN_OFFLOAD:
1589                     if (mBluetoothGatt != null && mBroadcastScanControlPoint != null) {
1590                         mBroadcastScanControlPoint.setValue(REMOTE_SCAN_STOP);
1591                         mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint);
1592                         mPendingOperation = message.what;
1593                         transitionTo(mConnectedProcessing);
1594                     } else {
1595                         log("no Bluetooth Gatt handle, may need to fetch write");
1596                     }
1597                     break;
1598                 case SELECT_BCAST_SOURCE:
1599                     ScanResult scanRes = (ScanResult) message.obj;
1600                     boolean auto = ((int) message.arg1) == BassConstants.AUTO;
1601                     selectSource(scanRes, auto);
1602                     break;
1603                 case ADD_BCAST_SOURCE:
1604                     metaData = (BluetoothLeBroadcastMetadata) message.obj;
1605                     log("Adding Broadcast source" + metaData);
1606                     byte[] addSourceInfo = convertMetadataToAddSourceByteArray(metaData);
1607                     if (addSourceInfo == null) {
1608                         Log.e(TAG, "add source: source Info is NULL");
1609                         break;
1610                     }
1611                     if (mBluetoothGatt != null && mBroadcastScanControlPoint != null) {
1612                         mBroadcastScanControlPoint.setValue(addSourceInfo);
1613                         mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint);
1614                         mPendingOperation = message.what;
1615                         mPendingMetadata = metaData;
1616                         if (metaData.isEncrypted() && (metaData.getBroadcastCode() != null)) {
1617                             mSetBroadcastCodePending = true;
1618                         }
1619                         transitionTo(mConnectedProcessing);
1620                         sendMessageDelayed(GATT_TXN_TIMEOUT, BassConstants.GATT_TXN_TIMEOUT_MS);
1621                     } else {
1622                         Log.e(TAG, "ADD_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal");
1623                         mService.getCallbacks().notifySourceAddFailed(mDevice,
1624                                 metaData, BluetoothStatusCodes.ERROR_UNKNOWN);
1625                     }
1626                     break;
1627                 case UPDATE_BCAST_SOURCE:
1628                     metaData = (BluetoothLeBroadcastMetadata) message.obj;
1629                     int sourceId = message.arg1;
1630                     int paSync = message.arg2;
1631                     log("Updating Broadcast source" + metaData);
1632                     byte[] updateSourceInfo = convertBroadcastMetadataToUpdateSourceByteArray(
1633                             sourceId, metaData, paSync);
1634                     if (updateSourceInfo == null) {
1635                         Log.e(TAG, "update source: source Info is NULL");
1636                         break;
1637                     }
1638                     if (mBluetoothGatt != null && mBroadcastScanControlPoint != null) {
1639                         mBroadcastScanControlPoint.setValue(updateSourceInfo);
1640                         mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint);
1641                         mPendingOperation = message.what;
1642                         mPendingSourceId = (byte) sourceId;
1643                         if (paSync == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE) {
1644                             setPendingRemove(sourceId, true);
1645                         }
1646                         if (metaData.isEncrypted() && (metaData.getBroadcastCode() != null)) {
1647                             mSetBroadcastCodePending = true;
1648                         }
1649                         mPendingMetadata = metaData;
1650                         transitionTo(mConnectedProcessing);
1651                         sendMessageDelayed(GATT_TXN_TIMEOUT, BassConstants.GATT_TXN_TIMEOUT_MS);
1652                     } else {
1653                         Log.e(TAG, "UPDATE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal");
1654                         mService.getCallbacks().notifySourceModifyFailed(
1655                                 mDevice, sourceId, BluetoothStatusCodes.ERROR_UNKNOWN);
1656                     }
1657                     break;
1658                 case SET_BCAST_CODE:
1659                     int argType = message.arg1;
1660                     mSetBroadcastCodePending = false;
1661                     BluetoothLeBroadcastReceiveState recvState = null;
1662                     if (argType == ARGTYPE_METADATA) {
1663                         mSetBroadcastPINMetadata =
1664                                 (BluetoothLeBroadcastMetadata) message.obj;
1665                         mSetBroadcastCodePending = true;
1666                     } else {
1667                         recvState = (BluetoothLeBroadcastReceiveState) message.obj;
1668                         if (!isItRightTimeToUpdateBroadcastPin(
1669                                 (byte) recvState.getSourceId())) {
1670                             mSetBroadcastCodePending = true;
1671                         }
1672                     }
1673                     if (mSetBroadcastCodePending == true) {
1674                         log("Ignore SET_BCAST now, but restore it for later");
1675                         break;
1676                     }
1677                     byte[] setBroadcastPINcmd =
1678                             convertRecvStateToSetBroadcastCodeByteArray(recvState);
1679                     if (setBroadcastPINcmd == null) {
1680                         Log.e(TAG, "SET_BCAST_CODE: Broadcast code is NULL");
1681                         break;
1682                     }
1683                     if (mBluetoothGatt != null && mBroadcastScanControlPoint != null) {
1684                         mBroadcastScanControlPoint.setValue(setBroadcastPINcmd);
1685                         mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint);
1686                         mPendingOperation = message.what;
1687                         mPendingSourceId = (byte) recvState.getSourceId();
1688                         transitionTo(mConnectedProcessing);
1689                         sendMessageDelayed(GATT_TXN_TIMEOUT, BassConstants.GATT_TXN_TIMEOUT_MS);
1690                     }
1691                     break;
1692                 case REMOVE_BCAST_SOURCE:
1693                     byte sid = (byte) message.arg1;
1694                     log("Removing Broadcast source, sourceId: " + sid);
1695                     byte[] removeSourceInfo = new byte[2];
1696                     removeSourceInfo[0] = OPCODE_REMOVE_SOURCE;
1697                     removeSourceInfo[1] = sid;
1698                     if (mBluetoothGatt != null && mBroadcastScanControlPoint != null) {
1699                         if (isPendingRemove((int) sid)) {
1700                             setPendingRemove((int) sid, false);
1701                         }
1702 
1703                         mBroadcastScanControlPoint.setValue(removeSourceInfo);
1704                         mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint);
1705                         mPendingOperation = message.what;
1706                         mPendingSourceId = sid;
1707                         transitionTo(mConnectedProcessing);
1708                         sendMessageDelayed(GATT_TXN_TIMEOUT, BassConstants.GATT_TXN_TIMEOUT_MS);
1709                     } else {
1710                         Log.e(TAG, "REMOVE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal");
1711                         mService.getCallbacks().notifySourceRemoveFailed(mDevice,
1712                                 sid, BluetoothStatusCodes.ERROR_UNKNOWN);
1713                     }
1714                     break;
1715                 case PSYNC_ACTIVE_TIMEOUT:
1716                     cancelActiveSync(null);
1717                     break;
1718                 default:
1719                     log("CONNECTED: not handled message:" + message.what);
1720                     return NOT_HANDLED;
1721             }
1722             return HANDLED;
1723         }
1724     }
1725 
isSuccess(int status)1726     private boolean isSuccess(int status) {
1727         boolean ret = false;
1728         switch (status) {
1729             case BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST:
1730             case BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST:
1731             case BluetoothStatusCodes.REASON_REMOTE_REQUEST:
1732             case BluetoothStatusCodes.REASON_SYSTEM_POLICY:
1733                 ret = true;
1734                 break;
1735             default:
1736                 break;
1737         }
1738         return ret;
1739     }
1740 
sendPendingCallbacks(int pendingOp, int status)1741     void sendPendingCallbacks(int pendingOp, int status) {
1742         switch (pendingOp) {
1743             case START_SCAN_OFFLOAD:
1744                 if (!isSuccess(status)) {
1745                     if (!mAutoTriggered) {
1746                         cancelActiveSync(null);
1747                     } else {
1748                         mAutoTriggered = false;
1749                     }
1750                 }
1751                 break;
1752             case ADD_BCAST_SOURCE:
1753                 if (!isSuccess(status)) {
1754                     cancelActiveSync(null);
1755                     Message message = obtainMessage(STOP_SCAN_OFFLOAD);
1756                     sendMessage(message);
1757                     mService.getCallbacks().notifySourceAddFailed(mDevice,
1758                             mPendingMetadata, status);
1759                     mPendingMetadata = null;
1760                 }
1761                 break;
1762             case UPDATE_BCAST_SOURCE:
1763                 if (!mAutoTriggered) {
1764                     if (!isSuccess(status)) {
1765                         mService.getCallbacks().notifySourceModifyFailed(mDevice,
1766                                 mPendingSourceId, status);
1767                         mPendingMetadata = null;
1768                     }
1769                 } else {
1770                     mAutoTriggered = false;
1771                 }
1772                 break;
1773             case REMOVE_BCAST_SOURCE:
1774                 if (!isSuccess(status)) {
1775                     mService.getCallbacks().notifySourceRemoveFailed(mDevice,
1776                             mPendingSourceId, status);
1777                 }
1778                 break;
1779             case SET_BCAST_CODE:
1780                 log("sendPendingCallbacks: SET_BCAST_CODE");
1781                 break;
1782             default:
1783                 log("sendPendingCallbacks: unhandled case");
1784                 break;
1785         }
1786     }
1787 
1788     // public for testing, but private for non-testing
1789     @VisibleForTesting
1790     class ConnectedProcessing extends State {
1791         @Override
enter()1792         public void enter() {
1793             log("Enter ConnectedProcessing(" + mDevice + "): "
1794                     + messageWhatToString(getCurrentMessage().what));
1795 
1796             // Broadcast for testing purpose only
1797             if (Utils.isInstrumentationTestMode()) {
1798                 Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST");
1799                 Utils.sendBroadcast(mService, intent, BLUETOOTH_CONNECT,
1800                         Utils.getTempAllowlistBroadcastOptions());
1801             }
1802         }
1803         @Override
exit()1804         public void exit() {
1805             /* Pending Metadata will be used to bond with source ID in receiver state notify */
1806             if (mPendingOperation == REMOVE_BCAST_SOURCE) {
1807                     mPendingMetadata = null;
1808             }
1809 
1810             log("Exit ConnectedProcessing(" + mDevice + "): "
1811                     + messageWhatToString(getCurrentMessage().what));
1812         }
1813         @Override
processMessage(Message message)1814         public boolean processMessage(Message message) {
1815             log("ConnectedProcessing process message(" + mDevice + "): "
1816                     + messageWhatToString(message.what));
1817             switch (message.what) {
1818                 case CONNECT:
1819                     Log.w(TAG, "CONNECT request is ignored" + mDevice);
1820                     break;
1821                 case DISCONNECT:
1822                     Log.w(TAG, "DISCONNECT requested!: " + mDevice);
1823                     mAllowReconnect = false;
1824                     if (mBluetoothGatt != null) {
1825                         mBluetoothGatt.disconnect();
1826                         mBluetoothGatt.close();
1827                         mBluetoothGatt = null;
1828                         cancelActiveSync(null);
1829                         transitionTo(mDisconnected);
1830                     } else {
1831                         log("mBluetoothGatt is null");
1832                     }
1833                     break;
1834                 case READ_BASS_CHARACTERISTICS:
1835                     Log.w(TAG, "defer READ_BASS_CHARACTERISTICS requested!: " + mDevice);
1836                     deferMessage(message);
1837                     break;
1838                 case CONNECTION_STATE_CHANGED:
1839                     int state = (int) message.obj;
1840                     Log.w(TAG, "ConnectedProcessing: connection state changed:" + state);
1841                     if (state == BluetoothProfile.STATE_CONNECTED) {
1842                         Log.w(TAG, "should never happen from this state");
1843                     } else {
1844                         Log.w(TAG, "Unexpected disconnection " + mDevice);
1845                         transitionTo(mDisconnected);
1846                     }
1847                     break;
1848                 case GATT_TXN_PROCESSED:
1849                     removeMessages(GATT_TXN_TIMEOUT);
1850                     int status = (int) message.arg1;
1851                     log("GATT transaction processed for" + mDevice);
1852                     if (status == BluetoothGatt.GATT_SUCCESS) {
1853                         sendPendingCallbacks(
1854                                 mPendingOperation,
1855                                 BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
1856                     } else {
1857                         sendPendingCallbacks(
1858                                 mPendingOperation,
1859                                 BluetoothStatusCodes.ERROR_UNKNOWN);
1860                     }
1861                     transitionTo(mConnected);
1862                     break;
1863                 case GATT_TXN_TIMEOUT:
1864                     log("GATT transaction timeout for" + mDevice);
1865                     sendPendingCallbacks(
1866                             mPendingOperation,
1867                             BluetoothStatusCodes.ERROR_UNKNOWN);
1868                     mPendingOperation = -1;
1869                     mPendingSourceId = -1;
1870                     transitionTo(mConnected);
1871                     break;
1872                 case START_SCAN_OFFLOAD:
1873                 case STOP_SCAN_OFFLOAD:
1874                 case SELECT_BCAST_SOURCE:
1875                 case ADD_BCAST_SOURCE:
1876                 case SET_BCAST_CODE:
1877                 case REMOVE_BCAST_SOURCE:
1878                 case PSYNC_ACTIVE_TIMEOUT:
1879                     log("defer the message:" + message.what + "so that it will be processed later");
1880                     deferMessage(message);
1881                     break;
1882                 default:
1883                     log("CONNECTEDPROCESSING: not handled message:" + message.what);
1884                     return NOT_HANDLED;
1885             }
1886             return HANDLED;
1887         }
1888     }
1889 
broadcastConnectionState(BluetoothDevice device, int fromState, int toState)1890     void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) {
1891         log("broadcastConnectionState " + device + ": " + fromState + "->" + toState);
1892         if (fromState == BluetoothProfile.STATE_CONNECTED
1893                 && toState == BluetoothProfile.STATE_CONNECTED) {
1894             log("CONNECTED->CONNECTED: Ignore");
1895             return;
1896         }
1897 
1898         Intent intent = new Intent(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED);
1899         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
1900         intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
1901         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
1902         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
1903                     | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
1904         Utils.sendBroadcast(mService, intent, BLUETOOTH_CONNECT,
1905                 Utils.getTempAllowlistBroadcastOptions());
1906     }
1907 
getConnectionState()1908     int getConnectionState() {
1909         String currentState = "Unknown";
1910         if (getCurrentState() != null) {
1911             currentState = getCurrentState().getName();
1912         }
1913         switch (currentState) {
1914             case "Disconnected":
1915                 log("Disconnected");
1916                 return BluetoothProfile.STATE_DISCONNECTED;
1917             case "Connecting":
1918                 log("Connecting");
1919                 return BluetoothProfile.STATE_CONNECTING;
1920             case "Connected":
1921             case "ConnectedProcessing":
1922                 log("connected");
1923                 return BluetoothProfile.STATE_CONNECTED;
1924             default:
1925                 Log.e(TAG, "Bad currentState: " + currentState);
1926                 return BluetoothProfile.STATE_DISCONNECTED;
1927         }
1928     }
1929 
getMaximumSourceCapacity()1930     int getMaximumSourceCapacity() {
1931         return mNumOfBroadcastReceiverStates;
1932     }
1933 
getDevice()1934     BluetoothDevice getDevice() {
1935         return mDevice;
1936     }
1937 
isConnected()1938     synchronized boolean isConnected() {
1939         return getCurrentState() == mConnected;
1940     }
1941 
messageWhatToString(int what)1942     public static String messageWhatToString(int what) {
1943         switch (what) {
1944             case CONNECT:
1945                 return "CONNECT";
1946             case DISCONNECT:
1947                 return "DISCONNECT";
1948             case CONNECTION_STATE_CHANGED:
1949                 return "CONNECTION_STATE_CHANGED";
1950             case GATT_TXN_PROCESSED:
1951                 return "GATT_TXN_PROCESSED";
1952             case READ_BASS_CHARACTERISTICS:
1953                 return "READ_BASS_CHARACTERISTICS";
1954             case START_SCAN_OFFLOAD:
1955                 return "START_SCAN_OFFLOAD";
1956             case STOP_SCAN_OFFLOAD:
1957                 return "STOP_SCAN_OFFLOAD";
1958             case ADD_BCAST_SOURCE:
1959                 return "ADD_BCAST_SOURCE";
1960             case SELECT_BCAST_SOURCE:
1961                 return "SELECT_BCAST_SOURCE";
1962             case UPDATE_BCAST_SOURCE:
1963                 return "UPDATE_BCAST_SOURCE";
1964             case SET_BCAST_CODE:
1965                 return "SET_BCAST_CODE";
1966             case REMOVE_BCAST_SOURCE:
1967                 return "REMOVE_BCAST_SOURCE";
1968             case PSYNC_ACTIVE_TIMEOUT:
1969                 return "PSYNC_ACTIVE_TIMEOUT";
1970             case CONNECT_TIMEOUT:
1971                 return "CONNECT_TIMEOUT";
1972             default:
1973                 break;
1974         }
1975         return Integer.toString(what);
1976     }
1977 
1978     /**
1979      * Dump info
1980      */
dump(StringBuilder sb)1981     public void dump(StringBuilder sb) {
1982         ProfileService.println(sb, "mDevice: " + mDevice);
1983         ProfileService.println(sb, "  StateMachine: " + this);
1984         // Dump the state machine logs
1985         StringWriter stringWriter = new StringWriter();
1986         PrintWriter printWriter = new PrintWriter(stringWriter);
1987         super.dump(new FileDescriptor(), printWriter, new String[] {});
1988         printWriter.flush();
1989         stringWriter.flush();
1990         ProfileService.println(sb, "  StateMachineLog:");
1991         Scanner scanner = new Scanner(stringWriter.toString());
1992         while (scanner.hasNextLine()) {
1993             String line = scanner.nextLine();
1994             ProfileService.println(sb, "    " + line);
1995         }
1996         scanner.close();
1997     }
1998 
1999     @Override
log(String msg)2000     protected void log(String msg) {
2001         if (BassConstants.BASS_DBG) {
2002             super.log(msg);
2003         }
2004     }
2005 
logByteArray(String prefix, byte[] value, int offset, int count)2006     private static void logByteArray(String prefix, byte[] value, int offset, int count) {
2007         StringBuilder builder = new StringBuilder(prefix);
2008         for (int i = offset; i < count; i++) {
2009             builder.append(String.format("0x%02X", value[i]));
2010             if (i != value.length - 1) {
2011                 builder.append(", ");
2012             }
2013         }
2014         Log.d(TAG, builder.toString());
2015     }
2016 
2017     /** Mockable wrapper of {@link BluetoothGatt}. */
2018     @VisibleForTesting
2019     public static class BluetoothGattTestableWrapper {
2020         public final BluetoothGatt mWrappedBluetoothGatt;
2021 
BluetoothGattTestableWrapper(BluetoothGatt bluetoothGatt)2022         BluetoothGattTestableWrapper(BluetoothGatt bluetoothGatt) {
2023             mWrappedBluetoothGatt = bluetoothGatt;
2024         }
2025 
2026         /** See {@link BluetoothGatt#getServices()}. */
getServices()2027         public List<BluetoothGattService> getServices() {
2028             return mWrappedBluetoothGatt.getServices();
2029         }
2030 
2031         /** See {@link BluetoothGatt#getService(UUID)}. */
2032         @Nullable
getService(UUID uuid)2033         public BluetoothGattService getService(UUID uuid) {
2034             return mWrappedBluetoothGatt.getService(uuid);
2035         }
2036 
2037         /** See {@link BluetoothGatt#discoverServices()}. */
discoverServices()2038         public boolean discoverServices() {
2039             return mWrappedBluetoothGatt.discoverServices();
2040         }
2041 
2042         /**
2043          * See {@link BluetoothGatt#readCharacteristic(
2044          * BluetoothGattCharacteristic)}.
2045          */
readCharacteristic(BluetoothGattCharacteristic characteristic)2046         public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
2047             return mWrappedBluetoothGatt.readCharacteristic(characteristic);
2048         }
2049 
2050         /**
2051          * See {@link BluetoothGatt#writeCharacteristic(
2052          * BluetoothGattCharacteristic, byte[], int)} .
2053          */
writeCharacteristic(BluetoothGattCharacteristic characteristic)2054         public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
2055             return mWrappedBluetoothGatt.writeCharacteristic(characteristic);
2056         }
2057 
2058         /** See {@link BluetoothGatt#readDescriptor(BluetoothGattDescriptor)}. */
readDescriptor(BluetoothGattDescriptor descriptor)2059         public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
2060             return mWrappedBluetoothGatt.readDescriptor(descriptor);
2061         }
2062 
2063         /**
2064          * See {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor,
2065          * byte[])}.
2066          */
writeDescriptor(BluetoothGattDescriptor descriptor)2067         public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
2068             return mWrappedBluetoothGatt.writeDescriptor(descriptor);
2069         }
2070 
2071         /** See {@link BluetoothGatt#requestMtu(int)}. */
requestMtu(int mtu)2072         public boolean requestMtu(int mtu) {
2073             return mWrappedBluetoothGatt.requestMtu(mtu);
2074         }
2075 
2076         /** See {@link BluetoothGatt#setCharacteristicNotification}. */
setCharacteristicNotification( BluetoothGattCharacteristic characteristic, boolean enable)2077         public boolean setCharacteristicNotification(
2078                 BluetoothGattCharacteristic characteristic, boolean enable) {
2079             return mWrappedBluetoothGatt.setCharacteristicNotification(characteristic, enable);
2080         }
2081 
2082         /** See {@link BluetoothGatt#disconnect()}. */
disconnect()2083         public void disconnect() {
2084             mWrappedBluetoothGatt.disconnect();
2085         }
2086 
2087         /** See {@link BluetoothGatt#close()}. */
close()2088         public void close() {
2089             mWrappedBluetoothGatt.close();
2090         }
2091     }
2092 
2093 }
2094