blob: 1d96f931896be12da1ceb919b277a1b0cf24a737 [file] [log] [blame]
Ram Periathiruvadide0ca082019-03-20 11:16:44 -07001/*
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 */
16package com.android.car.trust;
17
18import static android.bluetooth.BluetoothProfile.GATT_SERVER;
19
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothDevice;
22import android.bluetooth.BluetoothGatt;
23import android.bluetooth.BluetoothGattCharacteristic;
24import android.bluetooth.BluetoothGattServer;
25import android.bluetooth.BluetoothGattServerCallback;
26import android.bluetooth.BluetoothGattService;
27import android.bluetooth.BluetoothManager;
28import android.bluetooth.BluetoothProfile;
29import android.bluetooth.le.AdvertiseCallback;
30import android.bluetooth.le.AdvertiseData;
31import android.bluetooth.le.AdvertiseSettings;
32import android.bluetooth.le.BluetoothLeAdvertiser;
33import android.content.Context;
34import android.content.pm.PackageManager;
35import android.os.Handler;
36import android.os.ParcelUuid;
37import android.util.Log;
38
39import com.android.car.Utils;
40
41/**
42 * A generic class that manages BLE operations like start/stop advertising, notifying connects/
43 * disconnects and reading/writing values to GATT characteristics.
44 *
45 * TODO(b/123248433) This could move to a separate comms library.
46 */
47public abstract class BleManager {
48 private static final String TAG = BleManager.class.getSimpleName();
49
50 private static final int BLE_RETRY_LIMIT = 5;
51 private static final int BLE_RETRY_INTERVAL_MS = 1000;
52
53 private final Handler mHandler = new Handler();
54
55 private final Context mContext;
56 private BluetoothManager mBluetoothManager;
57 private BluetoothLeAdvertiser mAdvertiser;
58 private BluetoothGattServer mGattServer;
59 private int mAdvertiserStartCount;
60
61 BleManager(Context context) {
62 mContext = context;
63 }
64
65 /**
66 * Starts the GATT server with the given {@link BluetoothGattService} and begins
67 * advertising.
68 *
69 * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked.
70 * Therefore, several retries will be made to ensure advertising is started.
71 *
72 * @param service {@link BluetoothGattService} that will be discovered by clients
73 */
74 protected void startAdvertising(BluetoothGattService service,
75 AdvertiseCallback advertiseCallback) {
76 if (Log.isLoggable(TAG, Log.DEBUG)) {
77 Log.d(TAG, "startAdvertising: " + service.getUuid().toString());
78 }
79 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
80 Log.e(TAG, "System does not support BLE");
81 return;
82 }
83
84 // Only open one Gatt server.
85 if (mGattServer == null) {
86 if (Log.isLoggable(TAG, Log.DEBUG)) {
87 Log.d(TAG, "Opening a new GATT Server");
88 }
89 mBluetoothManager = (BluetoothManager) mContext.getSystemService(
90 Context.BLUETOOTH_SERVICE);
91 mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
92
93 if (mGattServer == null) {
94 Log.e(TAG, "Gatt Server not created");
95 return;
96 }
97 }
98
99 mGattServer.clearServices();
100 mGattServer.addService(service);
101
102 AdvertiseSettings settings = new AdvertiseSettings.Builder()
103 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
104 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
105 .setConnectable(true)
106 .build();
107
108 AdvertiseData data = new AdvertiseData.Builder()
109 .setIncludeDeviceName(true)
110 .addServiceUuid(new ParcelUuid(service.getUuid()))
111 .build();
112
113 mAdvertiserStartCount = 0;
114 startAdvertisingInternally(settings, data, advertiseCallback);
115 }
116
117 private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data,
118 AdvertiseCallback advertiseCallback) {
Ram Periathiruvadif9eef492019-03-28 18:09:39 -0700119 if (BluetoothAdapter.getDefaultAdapter() != null) {
120 mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
121 }
122
123 if (mAdvertiser != null) {
124 mAdvertiser.startAdvertising(settings, data, advertiseCallback);
125 mAdvertiserStartCount = 0;
126 } else if (mAdvertiserStartCount < BLE_RETRY_LIMIT) {
Ram Periathiruvadide0ca082019-03-20 11:16:44 -0700127 mHandler.postDelayed(
128 () -> startAdvertisingInternally(settings, data, advertiseCallback),
129 BLE_RETRY_INTERVAL_MS);
130 mAdvertiserStartCount += 1;
131 } else {
Ram Periathiruvadif9eef492019-03-28 18:09:39 -0700132 Log.e(TAG, "Cannot start BLE Advertisement. BT Adapter: "
133 + BluetoothAdapter.getDefaultAdapter() + " Advertise Retry count: "
134 + mAdvertiserStartCount);
Ram Periathiruvadide0ca082019-03-20 11:16:44 -0700135 }
136 }
137
138 protected void stopAdvertising(AdvertiseCallback advertiseCallback) {
139 if (mAdvertiser != null) {
140 if (Log.isLoggable(TAG, Log.DEBUG)) {
141 Log.d(TAG, "stopAdvertising: ");
142 }
143 mAdvertiser.stopAdvertising(advertiseCallback);
144 }
145 }
146
147 /**
148 * Notifies the characteristic change via {@link BluetoothGattServer}
149 */
150 protected void notifyCharacteristicChanged(BluetoothDevice device,
151 BluetoothGattCharacteristic characteristic, boolean confirm) {
152 if (mGattServer != null) {
153 mGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
154 }
155 }
156
157 protected Context getContext() {
158 return mContext;
159 }
160
161 /**
162 * Cleans up the BLE GATT server state.
163 */
164 void cleanup() {
165 // Stops the advertiser and GATT server. This needs to be done to avoid leaks
166 if (mAdvertiser != null) {
167 mAdvertiser.cleanup();
168 }
169
170 if (mGattServer != null) {
171 mGattServer.clearServices();
172 try {
173 for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) {
174 mGattServer.cancelConnection(d);
175 }
176 } catch (UnsupportedOperationException e) {
177 Log.e(TAG, "Error getting connected devices", e);
178 } finally {
179 stopGattServer();
180 }
181 }
182 }
183
184 /**
185 * Close the GATT Server
186 */
187 void stopGattServer() {
188 if (mGattServer == null) {
189 return;
190 }
191 if (Log.isLoggable(TAG, Log.DEBUG)) {
192 Log.d(TAG, "stopGattServer");
193 }
194 mGattServer.close();
195 mGattServer = null;
196 }
197
198 /**
199 * Triggered when a device (GATT client) connected.
200 *
201 * @param device Remote device that connected on BLE.
202 */
203 protected void onRemoteDeviceConnected(BluetoothDevice device) {
204 }
205
206 /**
207 * Triggered when a device (GATT client) disconnected.
208 *
209 * @param device Remote device that disconnected on BLE.
210 */
211 protected void onRemoteDeviceDisconnected(BluetoothDevice device) {
212 }
213
214 /**
215 * Triggered when this BleManager receives a write request from a remote
216 * device. Sub-classes should implement how to handle requests.
217 * <p>
218 *
219 * @see BluetoothGattServerCallback#onCharacteristicWriteRequest(BluetoothDevice, int,
220 * BluetoothGattCharacteristic, boolean, boolean, int, byte[])
221 */
222 protected abstract void onCharacteristicWrite(BluetoothDevice device, int requestId,
223 BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
224 responseNeeded, int offset, byte[] value);
225
226 /**
227 * Triggered when this BleManager receives a read request from a remote device.
228 * <p>
229 *
230 * @see BluetoothGattServerCallback#onCharacteristicReadRequest(BluetoothDevice, int, int,
231 * BluetoothGattCharacteristic)
232 */
233 protected abstract void onCharacteristicRead(BluetoothDevice device,
234 int requestId, int offset, BluetoothGattCharacteristic characteristic);
235
236 private final BluetoothGattServerCallback mGattServerCallback =
237 new BluetoothGattServerCallback() {
238 @Override
239 public void onConnectionStateChange(BluetoothDevice device, int status,
240 int newState) {
241 if (Log.isLoggable(TAG, Log.DEBUG)) {
242 Log.d(TAG, "BLE Connection State Change: " + newState);
243 }
244 switch (newState) {
245 case BluetoothProfile.STATE_CONNECTED:
246 onRemoteDeviceConnected(device);
247 break;
248 case BluetoothProfile.STATE_DISCONNECTED:
249 onRemoteDeviceDisconnected(device);
250 break;
251 default:
252 Log.w(TAG,
253 "Connection state not connecting or disconnecting; ignoring: "
254 + newState);
255 }
256 }
257
258 @Override
259 public void onServiceAdded(int status, BluetoothGattService service) {
260 if (Log.isLoggable(TAG, Log.DEBUG)) {
261 Log.d(TAG,
262 "Service added status: " + status + " uuid: " + service.getUuid());
263 }
264 }
265
266 @Override
267 public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
268 int offset, BluetoothGattCharacteristic characteristic) {
269 if (Log.isLoggable(TAG, Log.DEBUG)) {
270 Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid());
271 }
272
273 mGattServer.sendResponse(device, requestId,
274 BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
275 onCharacteristicRead(device, requestId, offset, characteristic);
276 }
277
278 @Override
279 public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
280 BluetoothGattCharacteristic characteristic, boolean preparedWrite,
281 boolean responseNeeded, int offset, byte[] value) {
282 if (Log.isLoggable(TAG, Log.DEBUG)) {
283 Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid()
284 + "value: " + Utils.byteArrayToHexString(value));
285 }
286
287 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
288 offset, value);
289 onCharacteristicWrite(device, requestId, characteristic,
290 preparedWrite, responseNeeded, offset, value);
291 }
292 };
293}