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 import static android.bluetooth.BluetoothGattService.SERVICE_TYPE_PRIMARY;
21 
22 import static java.util.concurrent.TimeUnit.SECONDS;
23 
24 import android.bluetooth.BluetoothAdapter;
25 import android.bluetooth.BluetoothDevice;
26 import android.bluetooth.BluetoothGattServer;
27 import android.bluetooth.BluetoothGattServerCallback;
28 import android.bluetooth.BluetoothGattService;
29 import android.bluetooth.BluetoothManager;
30 import android.bluetooth.BluetoothProfile;
31 import android.bluetooth.OobData;
32 import android.bluetooth.le.AdvertiseData;
33 import android.bluetooth.le.AdvertisingSetCallback;
34 import android.bluetooth.le.AdvertisingSetParameters;
35 import android.content.Context;
36 import android.os.Handler;
37 import android.os.Looper;
38 import android.os.ParcelUuid;
39 import android.util.Log;
40 
41 import java.util.List;
42 import java.util.UUID;
43 import java.util.concurrent.CountDownLatch;
44 import java.util.concurrent.Executors;
45 
46 public final class BluetoothGattMultiDevicesServer {
47     private static final String TAG = "BluetoothGattMultiDevicesServer";
48     private static final int CALLBACK_TIMEOUT_SEC = 1;
49 
50     private Context mContext;
51     private BluetoothManager mBluetoothManager;
52     private BluetoothAdapter mBluetoothAdapter;
53     private OobData mOobData;
54 
BluetoothGattMultiDevicesServer(Context context, BluetoothManager manager)55     public BluetoothGattMultiDevicesServer(Context context, BluetoothManager manager) {
56         mContext = context;
57         mBluetoothManager = manager;
58         mBluetoothAdapter = manager.getAdapter();
59     }
60 
createGattServer(String uuid)61     public BluetoothGattServer createGattServer(String uuid) {
62         var bluetoothGattServer =
63                 mBluetoothManager.openGattServer(mContext, new BluetoothGattServerCallback() {});
64         var service = new BluetoothGattService(UUID.fromString(uuid), SERVICE_TYPE_PRIMARY);
65         bluetoothGattServer.addService(service);
66         return bluetoothGattServer;
67     }
68 
getConnectedDevices()69     public List<BluetoothDevice> getConnectedDevices() {
70         return mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER);
71     }
72 
createAndAdvertiseServer(String uuid)73     public void createAndAdvertiseServer(String uuid) {
74         createGattServer(uuid);
75 
76         var bluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
77         var params = new AdvertisingSetParameters.Builder().setConnectable(true).build();
78         var data =
79                 new AdvertiseData.Builder()
80                         .addServiceUuid(new ParcelUuid(UUID.fromString(uuid)))
81                         .build();
82 
83         bluetoothLeAdvertiser.startAdvertisingSet(
84                 params, data, null, null, null, new AdvertisingSetCallback() {});
85     }
86 
createAndAdvertiseIsolatedServer(String uuid)87     public void createAndAdvertiseIsolatedServer(String uuid) {
88         var gattServer = createGattServer(uuid);
89 
90         var bluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
91         var params =
92                 new AdvertisingSetParameters.Builder()
93                         .setConnectable(true)
94                         .setOwnAddressType(
95                                 AdvertisingSetParameters.ADDRESS_TYPE_RANDOM_NON_RESOLVABLE)
96                         .build();
97         var data =
98                 new AdvertiseData.Builder()
99                         .addServiceUuid(new ParcelUuid(UUID.fromString(uuid)))
100                         .build();
101 
102         bluetoothLeAdvertiser.startAdvertisingSet(
103                 params,
104                 data,
105                 null,
106                 null,
107                 null,
108                 0,
109                 0,
110                 gattServer,
111                 new AdvertisingSetCallback() {},
112                 new Handler(Looper.getMainLooper()));
113     }
114 
115     private class OobDataCallbackImpl implements BluetoothAdapter.OobDataCallback {
116         private final CountDownLatch mCountDownLatch;
117 
OobDataCallbackImpl(CountDownLatch countDownLatch)118         OobDataCallbackImpl(CountDownLatch countDownLatch) {
119             mCountDownLatch = countDownLatch;
120         }
121 
122         @Override
onOobData(int transport, OobData oobData)123         public void onOobData(int transport, OobData oobData) {
124             Log.i(TAG, "OobDataCallback: onOobData: " + transport + ", " + oobData);
125             mOobData = oobData;
126             mCountDownLatch.countDown();
127 
128         }
129 
130         @Override
onError(int errorCode)131         public void onError(int errorCode) {
132             Log.i(TAG, "OobDataCallback: onError: " + errorCode);
133             mOobData = null;
134             mCountDownLatch.countDown();
135 
136         }
137     }
138 
generateLocalOObData()139     public OobData generateLocalOObData() {
140         CountDownLatch oobLatch = new CountDownLatch(1);
141         mBluetoothAdapter.generateLocalOobData(
142                 TRANSPORT_LE,
143                 Executors.newSingleThreadExecutor(),
144                 new OobDataCallbackImpl(oobLatch));
145         boolean timeout;
146         try {
147             timeout = !oobLatch.await(CALLBACK_TIMEOUT_SEC, SECONDS);
148         } catch (InterruptedException e) {
149             Log.e(TAG, "", e);
150             timeout = true;
151         }
152         if (timeout || mOobData == null) {
153             Log.e(TAG, "Did not generate local oob data");
154             return null;
155         }
156         return mOobData;
157     }
158 
159 }
160