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