diff options
Diffstat (limited to 'tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java')
-rw-r--r-- | tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java new file mode 100644 index 00000000..c0fecb31 --- /dev/null +++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java | |||
@@ -0,0 +1,355 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2018 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 | package com.android.car.trust.client; | ||
17 | |||
18 | import android.bluetooth.BluetoothDevice; | ||
19 | import android.bluetooth.BluetoothGatt; | ||
20 | import android.bluetooth.BluetoothGattCallback; | ||
21 | import android.bluetooth.BluetoothGattCharacteristic; | ||
22 | import android.bluetooth.BluetoothGattService; | ||
23 | import android.bluetooth.BluetoothManager; | ||
24 | import android.bluetooth.BluetoothProfile; | ||
25 | import android.bluetooth.le.BluetoothLeScanner; | ||
26 | import android.bluetooth.le.ScanCallback; | ||
27 | import android.bluetooth.le.ScanFilter; | ||
28 | import android.bluetooth.le.ScanResult; | ||
29 | import android.bluetooth.le.ScanSettings; | ||
30 | import android.content.Context; | ||
31 | import android.os.Handler; | ||
32 | import android.os.ParcelUuid; | ||
33 | import android.util.Log; | ||
34 | |||
35 | import androidx.annotation.NonNull; | ||
36 | |||
37 | import java.util.ArrayList; | ||
38 | import java.util.List; | ||
39 | import java.util.Queue; | ||
40 | import java.util.concurrent.ConcurrentLinkedQueue; | ||
41 | |||
42 | /** | ||
43 | * A simple client that supports the scanning and connecting to available BLE devices. Should be | ||
44 | * used along with {@link SimpleBleServer}. | ||
45 | */ | ||
46 | public class SimpleBleClient { | ||
47 | public interface ClientCallback { | ||
48 | /** | ||
49 | * Called when a device that has a matching service UUID is found. | ||
50 | **/ | ||
51 | void onDeviceConnected(BluetoothDevice device); | ||
52 | |||
53 | void onDeviceDisconnected(); | ||
54 | |||
55 | void onCharacteristicChanged(BluetoothGatt gatt, | ||
56 | BluetoothGattCharacteristic characteristic); | ||
57 | |||
58 | /** | ||
59 | * Called for each {@link BluetoothGattService} that is discovered on the | ||
60 | * {@link BluetoothDevice} after a matching scan result and connection. | ||
61 | * | ||
62 | * @param service {@link BluetoothGattService} that has been discovered. | ||
63 | */ | ||
64 | void onServiceDiscovered(BluetoothGattService service); | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * Wrapper class to allow queuing of BLE actions. The BLE stack allows only one action to be | ||
69 | * executed at a time. | ||
70 | */ | ||
71 | public static class BleAction { | ||
72 | public static final int ACTION_WRITE = 0; | ||
73 | public static final int ACTION_READ = 1; | ||
74 | |||
75 | private int mAction; | ||
76 | private BluetoothGattCharacteristic mCharacteristic; | ||
77 | |||
78 | public BleAction(BluetoothGattCharacteristic characteristic, int action) { | ||
79 | mAction = action; | ||
80 | mCharacteristic = characteristic; | ||
81 | } | ||
82 | |||
83 | public int getAction() { | ||
84 | return mAction; | ||
85 | } | ||
86 | |||
87 | public BluetoothGattCharacteristic getCharacteristic() { | ||
88 | return mCharacteristic; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | private static final long SCAN_TIME_MS = 10000; | ||
93 | |||
94 | private final Queue<BleAction> mBleActionQueue = new ConcurrentLinkedQueue<BleAction>(); | ||
95 | private final List<ClientCallback> mCallbacks = new ArrayList<>(); | ||
96 | private final Context mContext; | ||
97 | private final BluetoothLeScanner mScanner; | ||
98 | |||
99 | private BluetoothGatt mBtGatt; | ||
100 | private ParcelUuid mServiceUuid; | ||
101 | |||
102 | public SimpleBleClient(@NonNull Context context) { | ||
103 | mContext = context; | ||
104 | BluetoothManager btManager = (BluetoothManager) mContext.getSystemService( | ||
105 | Context.BLUETOOTH_SERVICE); | ||
106 | mScanner = btManager.getAdapter().getBluetoothLeScanner(); | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * Start scanning for a BLE devices with the specified service uuid. | ||
111 | * | ||
112 | * @param parcelUuid {@link ParcelUuid} used to identify the device that should be used for | ||
113 | * this client. This uuid should be the same as the one that is set in the | ||
114 | * {@link android.bluetooth.le.AdvertiseData.Builder} by the advertising | ||
115 | * device. | ||
116 | */ | ||
117 | public void start(ParcelUuid parcelUuid) { | ||
118 | mServiceUuid = parcelUuid; | ||
119 | |||
120 | // We only want to scan for devices that have the correct uuid set in its advertise data. | ||
121 | List<ScanFilter> filters = new ArrayList<ScanFilter>(); | ||
122 | ScanFilter.Builder serviceFilter = new ScanFilter.Builder(); | ||
123 | serviceFilter.setServiceUuid(mServiceUuid); | ||
124 | filters.add(serviceFilter.build()); | ||
125 | |||
126 | ScanSettings.Builder settings = new ScanSettings.Builder(); | ||
127 | settings.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY); | ||
128 | |||
129 | Log.d(Utils.LOG_TAG, "Start scanning for uuid: " + mServiceUuid.getUuid()); | ||
130 | mScanner.startScan(filters, settings.build(), mScanCallback); | ||
131 | |||
132 | Handler handler = new Handler(); | ||
133 | handler.postDelayed(new Runnable() { | ||
134 | @Override | ||
135 | public void run() { | ||
136 | mScanner.stopScan(mScanCallback); | ||
137 | Log.d(Utils.LOG_TAG, "Stopping Scanner"); | ||
138 | } | ||
139 | }, SCAN_TIME_MS); | ||
140 | } | ||
141 | |||
142 | private boolean hasServiceUuid(ScanResult result) { | ||
143 | if (result.getScanRecord() == null | ||
144 | || result.getScanRecord().getServiceUuids() == null | ||
145 | || result.getScanRecord().getServiceUuids().size() == 0) { | ||
146 | return false; | ||
147 | } | ||
148 | return true; | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * Writes to a {@link BluetoothGattCharacteristic} if possible, or queues the action until | ||
153 | * other actions are complete. | ||
154 | * | ||
155 | * @param characteristic {@link BluetoothGattCharacteristic} to be written | ||
156 | */ | ||
157 | public void writeCharacteristic(BluetoothGattCharacteristic characteristic) { | ||
158 | processAction(new BleAction(characteristic, BleAction.ACTION_WRITE)); | ||
159 | } | ||
160 | |||
161 | /** | ||
162 | * Reads a {@link BluetoothGattCharacteristic} if possible, or queues the read action until | ||
163 | * other actions are complete. | ||
164 | * | ||
165 | * @param characteristic {@link BluetoothGattCharacteristic} to be read. | ||
166 | */ | ||
167 | public void readCharacteristic(BluetoothGattCharacteristic characteristic) { | ||
168 | processAction(new BleAction(characteristic, BleAction.ACTION_READ)); | ||
169 | } | ||
170 | |||
171 | /** | ||
172 | * Enable or disable notification for specified {@link BluetoothGattCharacteristic}. | ||
173 | * | ||
174 | * @param characteristic The {@link BluetoothGattCharacteristic} for which to enable | ||
175 | * notifications. | ||
176 | * @param enabled True if notifications should be enabled, false otherwise. | ||
177 | */ | ||
178 | public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, | ||
179 | boolean enabled) { | ||
180 | mBtGatt.setCharacteristicNotification(characteristic, enabled); | ||
181 | } | ||
182 | |||
183 | /** | ||
184 | * Add a {@link ClientCallback} to listen for updates from BLE components | ||
185 | */ | ||
186 | public void addCallback(ClientCallback callback) { | ||
187 | mCallbacks.add(callback); | ||
188 | } | ||
189 | |||
190 | public void removeCallback(ClientCallback callback) { | ||
191 | mCallbacks.remove(callback); | ||
192 | } | ||
193 | |||
194 | private void processAction(BleAction action) { | ||
195 | // Only execute actions if the queue is empty. | ||
196 | if (mBleActionQueue.size() > 0) { | ||
197 | mBleActionQueue.add(action); | ||
198 | return; | ||
199 | } | ||
200 | |||
201 | mBleActionQueue.add(action); | ||
202 | executeAction(mBleActionQueue.peek()); | ||
203 | } | ||
204 | |||
205 | private void processNextAction() { | ||
206 | mBleActionQueue.poll(); | ||
207 | executeAction(mBleActionQueue.peek()); | ||
208 | } | ||
209 | |||
210 | private void executeAction(BleAction action) { | ||
211 | if (action == null) { | ||
212 | return; | ||
213 | } | ||
214 | |||
215 | Log.d(Utils.LOG_TAG, "Executing BLE Action type: " + action.getAction()); | ||
216 | int actionType = action.getAction(); | ||
217 | switch (actionType) { | ||
218 | case BleAction.ACTION_WRITE: | ||
219 | mBtGatt.writeCharacteristic(action.getCharacteristic()); | ||
220 | break; | ||
221 | case BleAction.ACTION_READ: | ||
222 | mBtGatt.readCharacteristic(action.getCharacteristic()); | ||
223 | break; | ||
224 | default: | ||
225 | } | ||
226 | } | ||
227 | |||
228 | private String getStatus(int status) { | ||
229 | switch (status) { | ||
230 | case BluetoothGatt.GATT_FAILURE: | ||
231 | return "Failure"; | ||
232 | case BluetoothGatt.GATT_SUCCESS: | ||
233 | return "GATT_SUCCESS"; | ||
234 | case BluetoothGatt.GATT_READ_NOT_PERMITTED: | ||
235 | return "GATT_READ_NOT_PERMITTED"; | ||
236 | case BluetoothGatt.GATT_WRITE_NOT_PERMITTED: | ||
237 | return "GATT_WRITE_NOT_PERMITTED"; | ||
238 | case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION: | ||
239 | return "GATT_INSUFFICIENT_AUTHENTICATION"; | ||
240 | case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED: | ||
241 | return "GATT_REQUEST_NOT_SUPPORTED"; | ||
242 | case BluetoothGatt.GATT_INVALID_OFFSET: | ||
243 | return "GATT_INVALID_OFFSET"; | ||
244 | case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH: | ||
245 | return "GATT_INVALID_ATTRIBUTE_LENGTH"; | ||
246 | case BluetoothGatt.GATT_CONNECTION_CONGESTED: | ||
247 | return "GATT_CONNECTION_CONGESTED"; | ||
248 | default: | ||
249 | return "unknown"; | ||
250 | } | ||
251 | } | ||
252 | |||
253 | private ScanCallback mScanCallback = new ScanCallback() { | ||
254 | @Override | ||
255 | public void onScanResult(int callbackType, ScanResult result) { | ||
256 | BluetoothDevice device = result.getDevice(); | ||
257 | Log.d(Utils.LOG_TAG, "Scan result found: " + result.getScanRecord().getServiceUuids()); | ||
258 | |||
259 | if (!hasServiceUuid(result)) { | ||
260 | return; | ||
261 | } | ||
262 | |||
263 | for (ParcelUuid uuid : result.getScanRecord().getServiceUuids()) { | ||
264 | Log.d(Utils.LOG_TAG, "Scan result UUID: " + uuid); | ||
265 | if (uuid.equals(mServiceUuid)) { | ||
266 | // This client only supports connecting to one service. | ||
267 | // Once we find one, stop scanning and open a GATT connection to the device. | ||
268 | mScanner.stopScan(mScanCallback); | ||
269 | mBtGatt = device.connectGatt(mContext, false /* autoConnect */, mGattCallback); | ||
270 | return; | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | |||
275 | @Override | ||
276 | public void onBatchScanResults(List<ScanResult> results) { | ||
277 | for (ScanResult r : results) { | ||
278 | Log.d(Utils.LOG_TAG, "Batch scanResult: " + r.getDevice().getName() | ||
279 | + " " + r.getDevice().getAddress()); | ||
280 | } | ||
281 | } | ||
282 | |||
283 | @Override | ||
284 | public void onScanFailed(int errorCode) { | ||
285 | Log.e(Utils.LOG_TAG, "Scan failed: " + errorCode); | ||
286 | } | ||
287 | }; | ||
288 | |||
289 | private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { | ||
290 | @Override | ||
291 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { | ||
292 | super.onConnectionStateChange(gatt, status, newState); | ||
293 | |||
294 | String state = ""; | ||
295 | |||
296 | if (newState == BluetoothProfile.STATE_CONNECTED) { | ||
297 | state = "Connected"; | ||
298 | mBtGatt.discoverServices(); | ||
299 | for (ClientCallback callback : mCallbacks) { | ||
300 | callback.onDeviceConnected(gatt.getDevice()); | ||
301 | } | ||
302 | |||
303 | } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { | ||
304 | state = "Disconnected"; | ||
305 | for (ClientCallback callback : mCallbacks) { | ||
306 | callback.onDeviceDisconnected(); | ||
307 | } | ||
308 | } | ||
309 | Log.d(Utils.LOG_TAG, "Gatt connection status: " + getStatus(status) | ||
310 | + " newState: " + state); | ||
311 | } | ||
312 | |||
313 | @Override | ||
314 | public void onServicesDiscovered(BluetoothGatt gatt, int status) { | ||
315 | super.onServicesDiscovered(gatt, status); | ||
316 | Log.d(Utils.LOG_TAG, "onServicesDiscovered: " + status); | ||
317 | |||
318 | List<BluetoothGattService> services = gatt.getServices(); | ||
319 | if (services == null || services.size() <= 0) { | ||
320 | return; | ||
321 | } | ||
322 | |||
323 | // Notify clients of newly discovered services. | ||
324 | for (BluetoothGattService service : mBtGatt.getServices()) { | ||
325 | Log.d(Utils.LOG_TAG, "Found service: " + service.getUuid() + " notifying clients"); | ||
326 | for (ClientCallback callback : mCallbacks) { | ||
327 | callback.onServiceDiscovered(service); | ||
328 | } | ||
329 | } | ||
330 | } | ||
331 | |||
332 | @Override | ||
333 | public void onCharacteristicWrite(BluetoothGatt gatt, | ||
334 | BluetoothGattCharacteristic characteristic, int status) { | ||
335 | Log.d(Utils.LOG_TAG, "onCharacteristicWrite: " + status); | ||
336 | processNextAction(); | ||
337 | } | ||
338 | |||
339 | @Override | ||
340 | public void onCharacteristicRead(BluetoothGatt gatt, | ||
341 | BluetoothGattCharacteristic characteristic, int status) { | ||
342 | Log.d(Utils.LOG_TAG, "onCharacteristicRead:" + new String(characteristic.getValue())); | ||
343 | processNextAction(); | ||
344 | } | ||
345 | |||
346 | @Override | ||
347 | public void onCharacteristicChanged(BluetoothGatt gatt, | ||
348 | BluetoothGattCharacteristic characteristic) { | ||
349 | for (ClientCallback callback : mCallbacks) { | ||
350 | callback.onCharacteristicChanged(gatt, characteristic); | ||
351 | } | ||
352 | processNextAction(); | ||
353 | } | ||
354 | }; | ||
355 | } | ||