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 */ 16 package com.android.car.trust; 17 18 import static android.bluetooth.BluetoothProfile.GATT_SERVER; 19 20 import android.app.Service; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothGatt; 24 import android.bluetooth.BluetoothGattCharacteristic; 25 import android.bluetooth.BluetoothGattServer; 26 import android.bluetooth.BluetoothGattServerCallback; 27 import android.bluetooth.BluetoothGattService; 28 import android.bluetooth.BluetoothManager; 29 import android.bluetooth.BluetoothProfile; 30 import android.bluetooth.le.AdvertiseCallback; 31 import android.bluetooth.le.AdvertiseData; 32 import android.bluetooth.le.AdvertiseSettings; 33 import android.bluetooth.le.BluetoothLeAdvertiser; 34 import android.content.Context; 35 import android.content.pm.PackageManager; 36 import android.os.Handler; 37 import android.os.ParcelUuid; 38 import android.util.Log; 39 40 /** 41 * A generic service to start a BLE 42 */ 43 public abstract class SimpleBleServer extends Service { 44 45 private static final String TAG = SimpleBleServer.class.getSimpleName(); 46 47 private static final int BLE_RETRY_LIMIT = 5; 48 private static final int BLE_RETRY_INTERVAL_MS = 1000; 49 50 private final AdvertiseCallback mAdvertisingCallback = new AdvertiseCallback() { 51 @Override 52 public void onStartSuccess(AdvertiseSettings settingsInEffect) { 53 super.onStartSuccess(settingsInEffect); 54 Log.d(TAG, "Successfully started advertising service"); 55 onAdvertiseStartSuccess(); 56 } 57 58 @Override 59 public void onStartFailure(int errorCode) { 60 super.onStartFailure(errorCode); 61 Log.e(TAG, "Failed to advertise, errorCode: " + errorCode); 62 onAdvertiseStartFailure(errorCode); 63 } 64 }; 65 66 private final BluetoothGattServerCallback mGattServerCallback = 67 new BluetoothGattServerCallback() { 68 @Override 69 public void onConnectionStateChange(BluetoothDevice device, 70 final int status, final int newState) { 71 Log.d(TAG, "GattServer connection change status: " + status 72 + " newState: " + newState 73 + " device name: " + device.getName()); 74 switch (newState) { 75 case BluetoothProfile.STATE_CONNECTED: 76 onAdvertiseDeviceConnected(device); 77 break; 78 case BluetoothProfile.STATE_DISCONNECTED: 79 onAdvertiseDeviceDisconnected(device); 80 break; 81 } 82 } 83 84 @Override 85 public void onServiceAdded(final int status, BluetoothGattService service) { 86 Log.d(TAG, "Service added status: " + status + " uuid: " + service.getUuid()); 87 } 88 89 @Override 90 public void onCharacteristicReadRequest(BluetoothDevice device, 91 int requestId, int offset, final BluetoothGattCharacteristic characteristic) { 92 Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid()); 93 mGattServer.sendResponse(device, requestId, 94 BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue()); 95 onCharacteristicRead(device, requestId, offset, characteristic); 96 } 97 98 @Override 99 public void onCharacteristicWriteRequest(final BluetoothDevice device, int requestId, 100 BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean 101 responseNeeded, int offset, byte[] value) { 102 Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid()); 103 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 104 offset, value); 105 onCharacteristicWrite(device, requestId, characteristic, 106 preparedWrite, responseNeeded, offset, value); 107 } 108 }; 109 110 private final Handler mHandler = new Handler(); 111 112 private BluetoothManager mBluetoothManager; 113 private BluetoothLeAdvertiser mAdvertiser; 114 private BluetoothGattServer mGattServer; 115 private int mAdvertiserStartCount; 116 117 /** 118 * Starts the GATT server with the given {@link BluetoothGattService} and begins 119 * advertising with the {@link ParcelUuid}. 120 * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked. 121 * Therefore, several retries will be made to ensure advertising is started. 122 * 123 * @param advertiseUuid Service Uuid used in the {@link AdvertiseData} 124 * @param service {@link BluetoothGattService} that will be discovered by clients 125 */ 126 protected void startAdvertising(ParcelUuid advertiseUuid, BluetoothGattService service) { 127 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { 128 Log.e(TAG, "System does not support BLE"); 129 return; 130 } 131 132 mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 133 mGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback); 134 if (mGattServer == null) { 135 Log.e(TAG, "Gatt Server not created"); 136 return; 137 } 138 139 // We only allow adding one service in this implementation. If multiple services need 140 // to be added, then they need to be queued up and added only after 141 // BluetoothGattServerCallback.onServiceAdded is called. 142 mGattServer.addService(service); 143 144 AdvertiseSettings settings = new AdvertiseSettings.Builder() 145 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) 146 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) 147 .setConnectable(true) 148 .build(); 149 150 AdvertiseData data = new AdvertiseData.Builder() 151 .setIncludeDeviceName(true) 152 .addServiceUuid(advertiseUuid) 153 .build(); 154 155 mAdvertiserStartCount = 0; 156 startAdvertisingInternally(settings, data); 157 } 158 159 private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data) { 160 mAdvertiserStartCount += 1; 161 mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser(); 162 if (mAdvertiser == null && mAdvertiserStartCount < BLE_RETRY_LIMIT) { 163 mHandler.postDelayed(() -> startAdvertisingInternally(settings, data), 164 BLE_RETRY_INTERVAL_MS); 165 } else { 166 mHandler.removeCallbacks(null); 167 mAdvertiser.startAdvertising(settings, data, mAdvertisingCallback); 168 mAdvertiserStartCount = 0; 169 } 170 } 171 172 protected void stopAdvertising() { 173 if (mAdvertiser != null) { 174 mAdvertiser.stopAdvertising(mAdvertisingCallback); 175 } 176 } 177 178 /** 179 * Notifies the characteristic change via {@link BluetoothGattServer} 180 */ 181 protected void notifyCharacteristicChanged(BluetoothDevice device, 182 BluetoothGattCharacteristic characteristic, boolean confirm) { 183 if (mGattServer != null) { 184 mGattServer.notifyCharacteristicChanged(device, characteristic, confirm); 185 } 186 } 187 188 @Override 189 public void onDestroy() { 190 // Stops the advertiser and GATT server. This needs to be done to avoid leaks 191 if (mAdvertiser != null) { 192 mAdvertiser.stopAdvertising(mAdvertisingCallback); 193 mAdvertiser.cleanup(); 194 } 195 196 if (mGattServer != null) { 197 mGattServer.clearServices(); 198 try { 199 for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) { 200 mGattServer.cancelConnection(d); 201 } 202 } catch (UnsupportedOperationException e) { 203 Log.e(TAG, "Error getting connected devices", e); 204 } finally { 205 mGattServer.close(); 206 } 207 } 208 super.onDestroy(); 209 } 210 211 // Delegate to subclass 212 protected void onAdvertiseStartSuccess() { } 213 protected void onAdvertiseStartFailure(int errorCode) { } 214 protected void onAdvertiseDeviceConnected(BluetoothDevice device) { } 215 protected void onAdvertiseDeviceDisconnected(BluetoothDevice device) { } 216 217 /** 218 * Triggered when this BleService receives a write request from a remote 219 * device. Sub-classes should implement how to handle requests. 220 */ 221 protected abstract void onCharacteristicWrite(BluetoothDevice device, int requestId, 222 BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean 223 responseNeeded, int offset, byte[] value); 224 225 /** 226 * Triggered when this BleService receives a read request from a remote device. 227 */ 228 protected abstract void onCharacteristicRead(BluetoothDevice device, 229 int requestId, int offset, final BluetoothGattCharacteristic characteristic); 230 231 } 232