1 /*
2  * Copyright 2023 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.google.snippet.bluetooth;
18 
19 import static android.bluetooth.BluetoothDevice.TRANSPORT_LE;
20 
21 import static java.util.concurrent.TimeUnit.SECONDS;
22 
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothDevice;
25 import android.bluetooth.BluetoothGatt;
26 import android.bluetooth.BluetoothGattCallback;
27 import android.bluetooth.BluetoothManager;
28 import android.bluetooth.BluetoothProfile;
29 import android.bluetooth.le.ScanCallback;
30 import android.bluetooth.le.ScanResult;
31 import android.bluetooth.le.ScanSettings;
32 import android.content.Context;
33 import android.os.ParcelUuid;
34 import android.util.Log;
35 
36 import java.util.UUID;
37 import java.util.concurrent.CountDownLatch;
38 
39 public final class BluetoothGattMultiDevicesClient {
40     private static final String TAG = "BluetoothGattMultiDevicesClient";
41 
42     private Context mContext;
43     private BluetoothAdapter mBluetoothAdapter;
44     private BluetoothGatt mBluetoothGatt;
45 
46     private CountDownLatch mConnectionBlocker = null;
47     private CountDownLatch mServicesDiscovered = null;
48 
49     private static final int CALLBACK_TIMEOUT_SEC = 60;
50 
51     private BluetoothDevice mServer;
52 
53     private final BluetoothGattCallback mGattCallback =
54             new BluetoothGattCallback() {
55                 @Override
56                 public void onConnectionStateChange(
57                         BluetoothGatt device, int status, int newState) {
58                     Log.i(TAG, "onConnectionStateChange: newState=" + newState);
59                     if (newState == BluetoothProfile.STATE_CONNECTED
60                             && mConnectionBlocker != null) {
61                         Log.v(TAG, "Connected");
62                         mConnectionBlocker.countDown();
63                     }
64                 }
65 
66                 @Override
67                 public void onServicesDiscovered(BluetoothGatt gatt, int status) {
68                     mServicesDiscovered.countDown();
69                 }
70             };
71 
BluetoothGattMultiDevicesClient(Context context, BluetoothManager manager)72     public BluetoothGattMultiDevicesClient(Context context, BluetoothManager manager) {
73         mContext = context;
74         mBluetoothAdapter = manager.getAdapter();
75     }
76 
connect(String uuid)77     public boolean connect(String uuid) {
78         // Scan for the peer
79         var serverFoundBlocker = new CountDownLatch(1);
80         var scanner = mBluetoothAdapter.getBluetoothLeScanner();
81         var callback =
82                 new ScanCallback() {
83                     @Override
84                     public void onScanResult(int callbackType, ScanResult result) {
85                         var uuids = result.getScanRecord().getServiceUuids();
86                         Log.v(TAG, "Found uuids " + uuids);
87                         if (uuids != null
88                                 && uuids.contains(new ParcelUuid(UUID.fromString(uuid)))) {
89                             mServer = result.getDevice();
90                             serverFoundBlocker.countDown();
91                         }
92                     }
93                 };
94         scanner.startScan(null, new ScanSettings.Builder().setLegacy(false).build(), callback);
95         boolean timeout = false;
96         try {
97             timeout = !serverFoundBlocker.await(CALLBACK_TIMEOUT_SEC, SECONDS);
98         } catch (InterruptedException e) {
99             Log.e(TAG, "", e);
100             timeout = true;
101         }
102         scanner.stopScan(callback);
103         if (timeout) {
104             Log.e(TAG, "Did not discover server");
105             return false;
106         }
107 
108         // Connect to the peer
109         mConnectionBlocker = new CountDownLatch(1);
110         mBluetoothGatt = mServer.connectGatt(mContext, false, mGattCallback, TRANSPORT_LE);
111         timeout = false;
112         try {
113             timeout = !mConnectionBlocker.await(CALLBACK_TIMEOUT_SEC, SECONDS);
114         } catch (InterruptedException e) {
115             Log.e(TAG, "", e);
116             timeout = true;
117         }
118         if (timeout) {
119             Log.e(TAG, "Did not connect to server");
120             return false;
121         }
122 
123         return true;
124     }
125 
containsService(String uuid)126     public boolean containsService(String uuid) {
127         mServicesDiscovered = new CountDownLatch(1);
128         mBluetoothGatt.discoverServices();
129         try {
130             mServicesDiscovered.await(CALLBACK_TIMEOUT_SEC, SECONDS);
131         } catch (InterruptedException e) {
132             Log.e(TAG, "", e);
133             return false;
134         }
135 
136         return mBluetoothGatt.getService(UUID.fromString(uuid)) != null;
137     }
138 }
139