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