• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 package com.android.car.trust;
17 
18 import static android.bluetooth.BluetoothProfile.GATT_SERVER;
19 
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothGatt;
23 import android.bluetooth.BluetoothGattCallback;
24 import android.bluetooth.BluetoothGattCharacteristic;
25 import android.bluetooth.BluetoothGattDescriptor;
26 import android.bluetooth.BluetoothGattServer;
27 import android.bluetooth.BluetoothGattServerCallback;
28 import android.bluetooth.BluetoothGattService;
29 import android.bluetooth.BluetoothManager;
30 import android.bluetooth.BluetoothProfile;
31 import android.bluetooth.le.AdvertiseCallback;
32 import android.bluetooth.le.AdvertiseData;
33 import android.bluetooth.le.AdvertiseSettings;
34 import android.bluetooth.le.BluetoothLeAdvertiser;
35 import android.content.Context;
36 import android.content.pm.PackageManager;
37 import android.os.Handler;
38 import android.util.Log;
39 
40 import androidx.annotation.Nullable;
41 
42 import com.android.car.Utils;
43 
44 import java.util.UUID;
45 
46 /**
47  * A generic class that manages BLE operations like start/stop advertising, notifying connects/
48  * disconnects and reading/writing values to GATT characteristics.
49  *
50  * TODO(b/123248433) This could move to a separate comms library.
51  */
52 public abstract class BleManager {
53     private static final String TAG = BleManager.class.getSimpleName();
54 
55     private static final int BLE_RETRY_LIMIT = 5;
56     private static final int BLE_RETRY_INTERVAL_MS = 1000;
57 
58     private static final int GATT_SERVER_RETRY_LIMIT = 20;
59     private static final int GATT_SERVER_RETRY_DELAY_MS = 200;
60 
61     // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth
62     // .service.generic_access.xml
63     private static final UUID GENERIC_ACCESS_PROFILE_UUID =
64             UUID.fromString("00001800-0000-1000-8000-00805f9b34fb");
65     //https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth
66     // .characteristic.gap.device_name.xml
67     private static final UUID DEVICE_NAME_UUID =
68             UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb");
69 
70     private final Handler mHandler = new Handler();
71 
72     private final Context mContext;
73     private BluetoothManager mBluetoothManager;
74     private BluetoothLeAdvertiser mAdvertiser;
75     private BluetoothGattServer mGattServer;
76     private BluetoothGatt mBluetoothGatt;
77     private int mAdvertiserStartCount;
78     private int mGattServerRetryStartCount;
79     private BluetoothGattService mBluetoothGattService;
80     private AdvertiseCallback mAdvertiseCallback;
81     private AdvertiseData mData;
82 
BleManager(Context context)83     BleManager(Context context) {
84         mContext = context;
85     }
86 
87     /**
88      * Starts the GATT server with the given {@link BluetoothGattService} and begins
89      * advertising.
90      *
91      * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked.
92      * Therefore, several retries will be made to ensure advertising is started.
93      *
94      * @param service           {@link BluetoothGattService} that will be discovered by clients
95      * @param data              {@link AdvertiseData} data to advertise
96      * @param advertiseCallback {@link AdvertiseCallback} callback for advertiser
97      */
startAdvertising(BluetoothGattService service, AdvertiseData data, AdvertiseCallback advertiseCallback)98     protected void startAdvertising(BluetoothGattService service, AdvertiseData data,
99             AdvertiseCallback advertiseCallback) {
100         if (Log.isLoggable(TAG, Log.DEBUG)) {
101             Log.d(TAG, "startAdvertising: " + service.getUuid().toString());
102         }
103         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
104             Log.e(TAG, "System does not support BLE");
105             return;
106         }
107 
108         mBluetoothGattService = service;
109         mAdvertiseCallback = advertiseCallback;
110         mData = data;
111         mGattServerRetryStartCount = 0;
112         mBluetoothManager = (BluetoothManager) mContext.getSystemService(
113             Context.BLUETOOTH_SERVICE);
114         openGattServer();
115     }
116 
openGattServer()117     private void openGattServer() {
118         // Only open one Gatt server.
119         if (mGattServer != null) {
120             if (Log.isLoggable(TAG, Log.DEBUG)) {
121                 Log.d(TAG, "Gatt Server created, retry count: " + mGattServerRetryStartCount);
122             }
123             mGattServer.clearServices();
124             mGattServer.addService(mBluetoothGattService);
125             AdvertiseSettings settings = new AdvertiseSettings.Builder()
126                 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
127                 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
128                 .setConnectable(true)
129                 .build();
130             mAdvertiserStartCount = 0;
131             startAdvertisingInternally(settings, mData, mAdvertiseCallback);
132             mGattServerRetryStartCount = 0;
133         } else if (mGattServerRetryStartCount < GATT_SERVER_RETRY_LIMIT) {
134             mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
135             mGattServerRetryStartCount++;
136             mHandler.postDelayed(() -> openGattServer(), GATT_SERVER_RETRY_DELAY_MS);
137         } else {
138             Log.e(TAG, "Gatt server not created - exceeded retry limit.");
139         }
140     }
141 
startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data, AdvertiseCallback advertiseCallback)142     private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data,
143             AdvertiseCallback advertiseCallback) {
144         if (BluetoothAdapter.getDefaultAdapter() != null) {
145             mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
146         }
147 
148         if (mAdvertiser != null) {
149             if (Log.isLoggable(TAG, Log.DEBUG)) {
150                 Log.d(TAG, "Advertiser created, retry count: " + mAdvertiserStartCount);
151             }
152             mAdvertiser.startAdvertising(settings, data, advertiseCallback);
153             mAdvertiserStartCount = 0;
154         } else if (mAdvertiserStartCount < BLE_RETRY_LIMIT) {
155             mHandler.postDelayed(
156                     () -> startAdvertisingInternally(settings, data, advertiseCallback),
157                     BLE_RETRY_INTERVAL_MS);
158             mAdvertiserStartCount += 1;
159         } else {
160             Log.e(TAG, "Cannot start BLE Advertisement.  BT Adapter: "
161                     + BluetoothAdapter.getDefaultAdapter() + " Advertise Retry count: "
162                     + mAdvertiserStartCount);
163         }
164     }
165 
stopAdvertising(AdvertiseCallback advertiseCallback)166     protected void stopAdvertising(AdvertiseCallback advertiseCallback) {
167         if (mAdvertiser != null) {
168             if (Log.isLoggable(TAG, Log.DEBUG)) {
169                 Log.d(TAG, "stopAdvertising: ");
170             }
171             mAdvertiser.stopAdvertising(advertiseCallback);
172         }
173     }
174 
175     /**
176      * Notifies the characteristic change via {@link BluetoothGattServer}
177      */
notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic, boolean confirm)178     protected void notifyCharacteristicChanged(BluetoothDevice device,
179             BluetoothGattCharacteristic characteristic, boolean confirm) {
180         if (mGattServer == null) {
181             return;
182         }
183 
184         boolean result = mGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
185 
186         if (Log.isLoggable(TAG, Log.DEBUG)) {
187             Log.d(TAG, "notifyCharacteristicChanged succeeded: " + result);
188         }
189     }
190 
191     /**
192      * Connect the Gatt server of the remote device to retrieve device name.
193      */
retrieveDeviceName(BluetoothDevice device)194     protected final void retrieveDeviceName(BluetoothDevice device) {
195         mBluetoothGatt = device.connectGatt(getContext(), false, mGattCallback);
196     }
197 
getContext()198     protected Context getContext() {
199         return mContext;
200     }
201 
202     /**
203      * Cleans up the BLE GATT server state.
204      */
cleanup()205     void cleanup() {
206         // Stops the advertiser and GATT server. This needs to be done to avoid leaks
207         if (mAdvertiser != null) {
208             mAdvertiser.cleanup();
209         }
210 
211         if (mGattServer != null) {
212             mGattServer.clearServices();
213             try {
214                 for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) {
215                     mGattServer.cancelConnection(d);
216                 }
217             } catch (UnsupportedOperationException e) {
218                 Log.e(TAG, "Error getting connected devices", e);
219             } finally {
220                 stopGattServer();
221             }
222         }
223     }
224 
225     /**
226      * Close the GATT Server
227      */
stopGattServer()228     void stopGattServer() {
229         if (mGattServer == null) {
230             return;
231         }
232         if (Log.isLoggable(TAG, Log.DEBUG)) {
233             Log.d(TAG, "stopGattServer");
234         }
235         if (mBluetoothGatt != null) {
236             mBluetoothGatt.disconnect();
237         }
238         mGattServer.close();
239         mGattServer = null;
240     }
241 
242     /**
243      * Triggered when the name of the remote device is retrieved.
244      *
245      * @param deviceName Name of the remote device.
246      */
onDeviceNameRetrieved(@ullable String deviceName)247     protected void onDeviceNameRetrieved(@Nullable String deviceName) {
248     }
249 
250     /**
251      * Triggered if a remote client has requested to change the MTU for a given connection.
252      *
253      * @param size The new MTU size.
254      */
onMtuSizeChanged(int size)255     protected void onMtuSizeChanged(int size) {
256     }
257 
258     /**
259      * Triggered when a device (GATT client) connected.
260      *
261      * @param device Remote device that connected on BLE.
262      */
onRemoteDeviceConnected(BluetoothDevice device)263     protected void onRemoteDeviceConnected(BluetoothDevice device) {
264     }
265 
266     /**
267      * Triggered when a device (GATT client) disconnected.
268      *
269      * @param device Remote device that disconnected on BLE.
270      */
onRemoteDeviceDisconnected(BluetoothDevice device)271     protected void onRemoteDeviceDisconnected(BluetoothDevice device) {
272     }
273 
274     /**
275      * Triggered when this BleManager receives a write request from a remote
276      * device. Sub-classes should implement how to handle requests.
277      * <p>
278      *
279      * @see BluetoothGattServerCallback#onCharacteristicWriteRequest(BluetoothDevice, int,
280      * BluetoothGattCharacteristic, boolean, boolean, int, byte[])
281      */
onCharacteristicWrite(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)282     protected abstract void onCharacteristicWrite(BluetoothDevice device, int requestId,
283             BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
284             responseNeeded, int offset, byte[] value);
285 
286     /**
287      * Triggered when this BleManager receives a read request from a remote device.
288      * <p>
289      *
290      * @see BluetoothGattServerCallback#onCharacteristicReadRequest(BluetoothDevice, int, int,
291      * BluetoothGattCharacteristic)
292      */
onCharacteristicRead(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic)293     protected abstract void onCharacteristicRead(BluetoothDevice device,
294             int requestId, int offset, BluetoothGattCharacteristic characteristic);
295 
296     private final BluetoothGattServerCallback mGattServerCallback =
297             new BluetoothGattServerCallback() {
298                 @Override
299                 public void onConnectionStateChange(BluetoothDevice device, int status,
300                         int newState) {
301                     if (Log.isLoggable(TAG, Log.DEBUG)) {
302                         Log.d(TAG, "BLE Connection State Change: " + newState);
303                     }
304                     switch (newState) {
305                         case BluetoothProfile.STATE_CONNECTED:
306                             onRemoteDeviceConnected(device);
307                             break;
308                         case BluetoothProfile.STATE_DISCONNECTED:
309                             onRemoteDeviceDisconnected(device);
310                             break;
311                         default:
312                             Log.w(TAG,
313                                     "Connection state not connecting or disconnecting; ignoring: "
314                                             + newState);
315                     }
316                 }
317 
318                 @Override
319                 public void onServiceAdded(int status, BluetoothGattService service) {
320                     if (Log.isLoggable(TAG, Log.DEBUG)) {
321                         Log.d(TAG,
322                                 "Service added status: " + status + " uuid: " + service.getUuid());
323                     }
324                 }
325 
326                 @Override
327                 public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
328                         int offset, BluetoothGattCharacteristic characteristic) {
329                     if (Log.isLoggable(TAG, Log.DEBUG)) {
330                         Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid());
331                     }
332 
333                     mGattServer.sendResponse(device, requestId,
334                             BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
335                     onCharacteristicRead(device, requestId, offset, characteristic);
336                 }
337 
338                 @Override
339                 public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
340                         BluetoothGattCharacteristic characteristic, boolean preparedWrite,
341                         boolean responseNeeded, int offset, byte[] value) {
342                     if (Log.isLoggable(TAG, Log.DEBUG)) {
343                         Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid()
344                                 + "value: " + Utils.byteArrayToHexString(value));
345                     }
346 
347                     mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
348                             offset, value);
349                     onCharacteristicWrite(device, requestId, characteristic,
350                             preparedWrite, responseNeeded, offset, value);
351                 }
352 
353                 @Override
354                 public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
355                         BluetoothGattDescriptor descriptor, boolean preparedWrite,
356                         boolean responseNeeded, int offset, byte[] value) {
357                     if (Log.isLoggable(TAG, Log.DEBUG)) {
358                         Log.d(TAG, "Write request for descriptor: " + descriptor.getUuid()
359                                 + "; value: " + Utils.byteArrayToHexString(value));
360                     }
361 
362                     mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
363                             offset, value);
364                 }
365 
366                 @Override
367                 public void onMtuChanged(BluetoothDevice device, int mtu) {
368                     if (Log.isLoggable(TAG, Log.DEBUG)) {
369                         Log.d(TAG, "onMtuChanged: " + mtu + " for device " + device.getAddress());
370                     }
371                     onMtuSizeChanged(mtu);
372                 }
373 
374             };
375 
376     private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
377         @Override
378         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
379             if (Log.isLoggable(TAG, Log.DEBUG)) {
380                 Log.d(TAG, "Gatt Connection State Change: " + newState);
381             }
382             switch (newState) {
383                 case BluetoothProfile.STATE_CONNECTED:
384                     if (Log.isLoggable(TAG, Log.DEBUG)) {
385                         Log.d(TAG, "Gatt connected");
386                     }
387                     mBluetoothGatt.discoverServices();
388                     break;
389                 case BluetoothProfile.STATE_DISCONNECTED:
390                     if (Log.isLoggable(TAG, Log.DEBUG)) {
391                         Log.d(TAG, "Gatt Disconnected");
392                     }
393                     break;
394                 default:
395                     if (Log.isLoggable(TAG, Log.DEBUG)) {
396                         Log.d(TAG,
397                                 "Connection state not connecting or disconnecting; ignoring: "
398                                         + newState);
399                     }
400             }
401         }
402 
403         @Override
404         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
405             if (Log.isLoggable(TAG, Log.DEBUG)) {
406                 Log.d(TAG, "Gatt Services Discovered");
407             }
408             BluetoothGattService gapService = mBluetoothGatt.getService(
409                     GENERIC_ACCESS_PROFILE_UUID);
410             if (gapService == null) {
411                 Log.e(TAG, "Generic Access Service is Null");
412                 return;
413             }
414             BluetoothGattCharacteristic deviceNameCharacteristic = gapService.getCharacteristic(
415                     DEVICE_NAME_UUID);
416             if (deviceNameCharacteristic == null) {
417                 Log.e(TAG, "Device Name Characteristic is Null");
418                 return;
419             }
420             mBluetoothGatt.readCharacteristic(deviceNameCharacteristic);
421         }
422 
423         @Override
424         public void onCharacteristicRead(BluetoothGatt gatt,
425                 BluetoothGattCharacteristic characteristic, int status) {
426             if (status == BluetoothGatt.GATT_SUCCESS) {
427                 String deviceName = characteristic.getStringValue(0);
428                 if (Log.isLoggable(TAG, Log.DEBUG)) {
429                     Log.d(TAG, "BLE Device Name: " + deviceName);
430                 }
431                 onDeviceNameRetrieved(deviceName);
432             } else {
433                 Log.e(TAG, "Reading GAP Failed: " + status);
434             }
435         }
436     };
437 }
438