SimpleBleClient.java revision 77e5e49cf9dcceb69b07510c380ae2a9285ebfee
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 Log.d(TAG, "Start scanning for uuid: " + mServiceUuid.getUuid()); 133 mScanner.startScan(filters, settings.build(), mScanCallback); 134 135 Handler handler = new Handler(); 136 handler.postDelayed(new Runnable() { 137 @Override 138 public void run() { 139 mScanner.stopScan(mScanCallback); 140 Log.d(TAG, "Stopping Scanner"); 141 } 142 }, SCAN_TIME_MS); 143 } 144 145 private boolean hasServiceUuid(ScanResult result) { 146 if (result.getScanRecord() == null 147 || result.getScanRecord().getServiceUuids() == null 148 || result.getScanRecord().getServiceUuids().size() == 0) { 149 return false; 150 } 151 return true; 152 } 153 154 /** 155 * Writes to a {@link BluetoothGattCharacteristic} if possible, or queues the action until 156 * other actions are complete. 157 * 158 * @param characteristic {@link BluetoothGattCharacteristic} to be written 159 */ 160 public void writeCharacteristic(BluetoothGattCharacteristic characteristic) { 161 processAction(new BleAction(characteristic, BleAction.ACTION_WRITE)); 162 } 163 164 /** 165 * Reads a {@link BluetoothGattCharacteristic} if possible, or queues the read action until 166 * other actions are complete. 167 * 168 * @param characteristic {@link BluetoothGattCharacteristic} to be read. 169 */ 170 public void readCharacteristic(BluetoothGattCharacteristic characteristic) { 171 processAction(new BleAction(characteristic, BleAction.ACTION_READ)); 172 } 173 174 /** 175 * Enable or disable notification for specified {@link BluetoothGattCharacteristic}. 176 * 177 * @param characteristic The {@link BluetoothGattCharacteristic} for which to enable 178 * notifications. 179 * @param enabled True if notifications should be enabled, false otherwise. 180 */ 181 public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, 182 boolean enabled) { 183 mBtGatt.setCharacteristicNotification(characteristic, enabled); 184 } 185 186 /** 187 * Add a {@link ClientCallback} to listen for updates from BLE components 188 */ 189 public void addCallback(ClientCallback callback) { 190 mCallbacks.add(callback); 191 } 192 193 public void removeCallback(ClientCallback callback) { 194 mCallbacks.remove(callback); 195 } 196 197 private void processAction(BleAction action) { 198 // Only execute actions if the queue is empty. 199 if (mBleActionQueue.size() > 0) { 200 mBleActionQueue.add(action); 201 return; 202 } 203 204 mBleActionQueue.add(action); 205 executeAction(mBleActionQueue.peek()); 206 } 207 208 private void processNextAction() { 209 mBleActionQueue.poll(); 210 executeAction(mBleActionQueue.peek()); 211 } 212 213 private void executeAction(BleAction action) { 214 if (action == null) { 215 return; 216 } 217 218 Log.d(TAG, "Executing BLE Action type: " + action.getAction()); 219 220 int actionType = action.getAction(); 221 switch (actionType) { 222 case BleAction.ACTION_WRITE: 223 mBtGatt.writeCharacteristic(action.getCharacteristic()); 224 break; 225 case BleAction.ACTION_READ: 226 mBtGatt.readCharacteristic(action.getCharacteristic()); 227 break; 228 default: 229 } 230 } 231 232 private String getStatus(int status) { 233 switch (status) { 234 case BluetoothGatt.GATT_FAILURE: 235 return "Failure"; 236 case BluetoothGatt.GATT_SUCCESS: 237 return "GATT_SUCCESS"; 238 case BluetoothGatt.GATT_READ_NOT_PERMITTED: 239 return "GATT_READ_NOT_PERMITTED"; 240 case BluetoothGatt.GATT_WRITE_NOT_PERMITTED: 241 return "GATT_WRITE_NOT_PERMITTED"; 242 case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION: 243 return "GATT_INSUFFICIENT_AUTHENTICATION"; 244 case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED: 245 return "GATT_REQUEST_NOT_SUPPORTED"; 246 case BluetoothGatt.GATT_INVALID_OFFSET: 247 return "GATT_INVALID_OFFSET"; 248 case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH: 249 return "GATT_INVALID_ATTRIBUTE_LENGTH"; 250 case BluetoothGatt.GATT_CONNECTION_CONGESTED: 251 return "GATT_CONNECTION_CONGESTED"; 252 default: 253 return "unknown"; 254 } 255 } 256 257 private ScanCallback mScanCallback = new ScanCallback() { 258 @Override 259 public void onScanResult(int callbackType, ScanResult result) { 260 BluetoothDevice device = result.getDevice(); 261 Log.d(TAG, "Scan result found: " + result.getScanRecord().getServiceUuids()); 262 263 if (!hasServiceUuid(result)) { 264 return; 265 } 266 267 for (ParcelUuid uuid : result.getScanRecord().getServiceUuids()) { 268 Log.d(TAG, "Scan result UUID: " + uuid); 269 if (uuid.equals(mServiceUuid)) { 270 // This client only supports connecting to one service. 271 // Once we find one, stop scanning and open a GATT connection to the device. 272 mScanner.stopScan(mScanCallback); 273 mBtGatt = device.connectGatt(mContext, false /* autoConnect */, mGattCallback); 274 return; 275 } 276 } 277 } 278 279 @Override 280 public void onBatchScanResults(List<ScanResult> results) { 281 for (ScanResult r : results) { 282 Log.d(TAG, "Batch scanResult: " 283 + r.getDevice().getName() + " " + r.getDevice().getAddress()); 284 } 285 } 286 287 @Override 288 public void onScanFailed(int errorCode) { 289 Log.d(TAG, "Scan failed: " + errorCode); 290 } 291 }; 292 293 private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { 294 @Override 295 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 296 super.onConnectionStateChange(gatt, status, newState); 297 298 String state = ""; 299 300 if (newState == BluetoothProfile.STATE_CONNECTED) { 301 state = "Connected"; 302 mBtGatt.discoverServices(); 303 for (ClientCallback callback : mCallbacks) { 304 callback.onDeviceConnected(gatt.getDevice()); 305 } 306 307 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 308 state = "Disconnected"; 309 for (ClientCallback callback : mCallbacks) { 310 callback.onDeviceDisconnected(); 311 } 312 } 313 Log.d(TAG, " Gatt connection status: " + getStatus(status) + " newState: " + state); 314 } 315 316 @Override 317 public void onServicesDiscovered(BluetoothGatt gatt, int status) { 318 super.onServicesDiscovered(gatt, status); 319 Log.d(TAG, "onServicesDiscovered: " + status); 320 321 List<BluetoothGattService> services = gatt.getServices(); 322 if (services == null || services.size() <= 0) { 323 return; 324 } 325 326 // Notify clients of newly discovered services. 327 for (BluetoothGattService service : mBtGatt.getServices()) { 328 Log.d(TAG, "Found service: " + service.getUuid() + " notifying clients"); 329 for (ClientCallback callback : mCallbacks) { 330 callback.onServiceDiscovered(service); 331 } 332 } 333 } 334 335 @Override 336 public void onCharacteristicWrite(BluetoothGatt gatt, 337 BluetoothGattCharacteristic characteristic, int status) { 338 Log.d(TAG, "onCharacteristicWrite: " + status); 339 processNextAction(); 340 } 341 342 @Override 343 public void onCharacteristicRead(BluetoothGatt gatt, 344 BluetoothGattCharacteristic characteristic, int status) { 345 Log.d(TAG, "onCharacteristicRead:" + new String(characteristic.getValue())); 346 processNextAction(); 347 } 348 349 @Override 350 public void onCharacteristicChanged(BluetoothGatt gatt, 351 BluetoothGattCharacteristic characteristic) { 352 for (ClientCallback callback : mCallbacks) { 353 callback.onCharacteristicChanged(gatt, characteristic); 354 } 355 processNextAction(); 356 } 357 }; 358 359} 360