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