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