1 /* 2 * Copyright (C) 2019 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.car.connecteddevice.ble; 18 19 import static com.android.car.connecteddevice.util.SafeLog.logd; 20 import static com.android.car.connecteddevice.util.SafeLog.logw; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.bluetooth.BluetoothDevice; 25 import android.bluetooth.BluetoothGatt; 26 27 import com.android.car.connecteddevice.storage.ConnectedDeviceStorage; 28 import com.android.car.connecteddevice.util.ThreadSafeCallbacks; 29 30 import java.util.concurrent.CopyOnWriteArraySet; 31 import java.util.concurrent.Executor; 32 33 /** 34 * Generic BLE manager for a car that keeps track of connected devices and their associated 35 * callbacks. 36 */ 37 public abstract class CarBleManager { 38 39 private static final String TAG = "CarBleManager"; 40 41 final ConnectedDeviceStorage mStorage; 42 43 final CopyOnWriteArraySet<BleDevice> mConnectedDevices = new CopyOnWriteArraySet<>(); 44 45 final ThreadSafeCallbacks<Callback> mCallbacks = new ThreadSafeCallbacks<>(); 46 CarBleManager(@onNull ConnectedDeviceStorage connectedDeviceStorage)47 protected CarBleManager(@NonNull ConnectedDeviceStorage connectedDeviceStorage) { 48 mStorage = connectedDeviceStorage; 49 } 50 51 /** 52 * Initialize and start the manager. 53 */ start()54 public void start() { 55 } 56 57 /** 58 * Stop the manager and clean up. 59 */ stop()60 public void stop() { 61 for (BleDevice device : mConnectedDevices) { 62 if (device.mGatt != null) { 63 device.mGatt.close(); 64 } 65 } 66 mConnectedDevices.clear(); 67 } 68 69 /** 70 * Register a {@link Callback} to be notified on the {@link Executor}. 71 */ registerCallback(@onNull Callback callback, @NonNull Executor executor)72 public void registerCallback(@NonNull Callback callback, @NonNull Executor executor) { 73 mCallbacks.add(callback, executor); 74 } 75 76 /** 77 * Unregister a callback. 78 * 79 * @param callback The {@link Callback} to unregister. 80 */ unregisterCallback(@onNull Callback callback)81 public void unregisterCallback(@NonNull Callback callback) { 82 mCallbacks.remove(callback); 83 } 84 85 /** 86 * Send a message to a connected device. 87 * 88 * @param deviceId Id of connected device. 89 * @param message {@link DeviceMessage} to send. 90 */ sendMessage(@onNull String deviceId, @NonNull DeviceMessage message)91 public void sendMessage(@NonNull String deviceId, @NonNull DeviceMessage message) { 92 BleDevice device = getConnectedDevice(deviceId); 93 if (device == null) { 94 logw(TAG, "Attempted to send message to unknown device $deviceId. Ignored."); 95 return; 96 } 97 98 sendMessage(device, message); 99 } 100 101 /** 102 * Send a message to a connected device. 103 * 104 * @param device The connected {@link BleDevice}. 105 * @param message {@link DeviceMessage} to send. 106 */ sendMessage(@onNull BleDevice device, @NonNull DeviceMessage message)107 public void sendMessage(@NonNull BleDevice device, @NonNull DeviceMessage message) { 108 String deviceId = device.mDeviceId; 109 if (deviceId == null) { 110 deviceId = "Unidentified device"; 111 } 112 113 logd(TAG, "Writing " + message.getMessage().length + " bytes to " + deviceId + "."); 114 115 116 if (message.isMessageEncrypted()) { 117 device.mSecureChannel.sendEncryptedMessage(message); 118 } else { 119 device.mSecureChannel.getStream().writeMessage(message); 120 } 121 } 122 123 /** 124 * Get the {@link BleDevice} with matching {@link BluetoothGatt} if available. Returns 125 * {@code null} if no matches are found. 126 */ 127 @Nullable getConnectedDevice(@onNull BluetoothGatt gatt)128 BleDevice getConnectedDevice(@NonNull BluetoothGatt gatt) { 129 for (BleDevice device : mConnectedDevices) { 130 if (device.mGatt == gatt) { 131 return device; 132 } 133 } 134 135 return null; 136 } 137 138 /** 139 * Get the {@link BleDevice} with matching {@link BluetoothDevice} if available. Returns 140 * {@code null} if no matches are found. 141 */ 142 @Nullable getConnectedDevice(@onNull BluetoothDevice device)143 BleDevice getConnectedDevice(@NonNull BluetoothDevice device) { 144 for (BleDevice connectedDevice : mConnectedDevices) { 145 if (device.equals(connectedDevice.mDevice)) { 146 return connectedDevice; 147 } 148 } 149 150 return null; 151 } 152 153 /** 154 * Get the {@link BleDevice} with matching device id if available. Returns {@code null} if 155 * no matches are found. 156 */ 157 @Nullable getConnectedDevice(@onNull String deviceId)158 BleDevice getConnectedDevice(@NonNull String deviceId) { 159 for (BleDevice device : mConnectedDevices) { 160 if (deviceId.equals(device.mDeviceId)) { 161 return device; 162 } 163 } 164 165 return null; 166 } 167 168 /** Add the {@link BleDevice} that has connected. */ addConnectedDevice(@onNull BleDevice device)169 void addConnectedDevice(@NonNull BleDevice device) { 170 mConnectedDevices.add(device); 171 } 172 173 /** Return the number of devices currently connected. */ getConnectedDevicesCount()174 int getConnectedDevicesCount() { 175 return mConnectedDevices.size(); 176 } 177 178 /** Remove [@link BleDevice} that has been disconnected. */ removeConnectedDevice(@onNull BleDevice device)179 void removeConnectedDevice(@NonNull BleDevice device) { 180 mConnectedDevices.remove(device); 181 } 182 183 /** Disconnect the provided device from this manager. */ disconnectDevice(@onNull String deviceId)184 public abstract void disconnectDevice(@NonNull String deviceId); 185 186 /** State for a connected device. */ 187 enum BleDeviceState { 188 CONNECTING, 189 PENDING_VERIFICATION, 190 CONNECTED, 191 UNKNOWN 192 } 193 194 /** 195 * Container class to hold information about a connected device. 196 */ 197 static class BleDevice { 198 199 BluetoothDevice mDevice; 200 BluetoothGatt mGatt; 201 BleDeviceState mState; 202 String mDeviceId; 203 SecureBleChannel mSecureChannel; 204 BleDevice(@onNull BluetoothDevice device, @Nullable BluetoothGatt gatt)205 BleDevice(@NonNull BluetoothDevice device, @Nullable BluetoothGatt gatt) { 206 mDevice = device; 207 mGatt = gatt; 208 mState = BleDeviceState.UNKNOWN; 209 } 210 } 211 212 /** 213 * Callback for triggered events from {@link CarBleManager}. 214 */ 215 public interface Callback { 216 /** 217 * Triggered when device is connected and device id retrieved. Device is now ready to 218 * receive messages. 219 * 220 * @param deviceId Id of device that has connected. 221 */ onDeviceConnected(@onNull String deviceId)222 void onDeviceConnected(@NonNull String deviceId); 223 224 /** 225 * Triggered when device is disconnected. 226 * 227 * @param deviceId Id of device that has disconnected. 228 */ onDeviceDisconnected(@onNull String deviceId)229 void onDeviceDisconnected(@NonNull String deviceId); 230 231 /** 232 * Triggered when device has established encryption for secure communication. 233 * 234 * @param deviceId Id of device that has established encryption. 235 */ onSecureChannelEstablished(@onNull String deviceId)236 void onSecureChannelEstablished(@NonNull String deviceId); 237 238 /** 239 * Triggered when a new message is received. 240 * 241 * @param deviceId Id of the device that sent the message. 242 * @param message {@link DeviceMessage} received. 243 */ onMessageReceived(@onNull String deviceId, @NonNull DeviceMessage message)244 void onMessageReceived(@NonNull String deviceId, @NonNull DeviceMessage message); 245 246 /** 247 * Triggered when an error when establishing the secure channel. 248 * 249 * @param deviceId Id of the device that experienced the error. 250 */ onSecureChannelError(@onNull String deviceId)251 void onSecureChannelError(@NonNull String deviceId); 252 } 253 } 254