2 * Copyright (c) 2020 Project CHIP Authors
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package chip.devicecontroller;
20 import android.bluetooth.BluetoothGatt;
21 import android.bluetooth.BluetoothGattCallback;
22 import android.bluetooth.BluetoothGattCharacteristic;
23 import android.bluetooth.BluetoothGattDescriptor;
24 import android.bluetooth.BluetoothGattService;
25 import android.bluetooth.BluetoothProfile;
26 import android.os.Build;
27 import android.util.Log;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.UUID;
33 * AndroidChipStack - a singleton object shared between all instances of
34 * ChipDeviceController.
36 * Holds list of active connections with mapping to the associated
39 public final class AndroidChipStack {
41 private static final String TAG = AndroidChipStack.class.getSimpleName();
42 public static final int INITIAL_CONNECTIONS = 4;
44 private static class BleMtuDenylist {
46 * Will be set at initialization to indicate whether the device on which this code is being run
47 * is known to indicate unreliable MTU values for Bluetooth LE connections.
49 static final boolean BLE_MTU_DENYLISTED;
52 * If {@link #BLE_MTU_DENYLISTED} is true, then this is the fallback MTU to use for this device
54 static final int BLE_MTU_FALLBACK = 23;
57 if ("OnePlus".equals(Build.MANUFACTURER)) {
58 BLE_MTU_DENYLISTED = "ONE A2005".equals(Build.MODEL) ? true : false;
59 } else if ("motorola".equals(Build.MANUFACTURER)) {
61 "XT1575".equals(Build.MODEL) || "XT1585".equals(Build.MODEL) ? true : false;
63 BLE_MTU_DENYLISTED = false;
68 /* Singleton instance of this class */
69 private static final AndroidChipStack sInstance = new AndroidChipStack();
71 /* Mapping of connections to connection objects */
72 private final List<ChipDeviceController> mConnections;
74 private BluetoothGattCallback mGattCallback;
76 private AndroidChipStack() {
77 mConnections = new ArrayList<ChipDeviceController>(INITIAL_CONNECTIONS);
79 new BluetoothGattCallback() {
81 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
84 if (newState == BluetoothProfile.STATE_DISCONNECTED) {
85 connId = getConnId(gatt);
87 Log.d(TAG, "onConnectionStateChange Disconnected");
88 handleConnectionError(connId);
90 Log.e(TAG, "onConnectionStateChange disconnected: no active connection");
96 public void onServicesDiscovered(BluetoothGatt gatt, int status) {}
99 public void onCharacteristicRead(
100 BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
103 public void onCharacteristicWrite(
104 BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
105 byte[] svcIdBytes = convertUUIDToBytes(characteristic.getService().getUuid());
106 byte[] charIdBytes = convertUUIDToBytes(characteristic.getUuid());
108 if (status != BluetoothGatt.GATT_SUCCESS) {
111 "onCharacteristicWrite for "
112 + characteristic.getUuid().toString()
113 + " failed with status: "
118 int connId = getConnId(gatt);
120 handleWriteConfirmation(
121 connId, svcIdBytes, charIdBytes, status == BluetoothGatt.GATT_SUCCESS);
123 Log.e(TAG, "onCharacteristicWrite no active connection");
129 public void onCharacteristicChanged(
130 BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
131 byte[] svcIdBytes = convertUUIDToBytes(characteristic.getService().getUuid());
132 byte[] charIdBytes = convertUUIDToBytes(characteristic.getUuid());
133 int connId = getConnId(gatt);
135 handleIndicationReceived(connId, svcIdBytes, charIdBytes, characteristic.getValue());
137 Log.e(TAG, "onCharacteristicChanged no active connection");
143 public void onDescriptorWrite(
144 BluetoothGatt gatt, BluetoothGattDescriptor desc, int status) {
145 BluetoothGattCharacteristic characteristic = desc.getCharacteristic();
147 byte[] svcIdBytes = convertUUIDToBytes(characteristic.getService().getUuid());
148 byte[] charIdBytes = convertUUIDToBytes(characteristic.getUuid());
150 if (status != BluetoothGatt.GATT_SUCCESS) {
153 "onDescriptorWrite for "
154 + desc.getUuid().toString()
155 + " failed with status: "
159 int connId = getConnId(gatt);
161 Log.e(TAG, "onDescriptorWrite no active connection");
165 if (desc.getValue() == BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) {
166 handleSubscribeComplete(
167 connId, svcIdBytes, charIdBytes, status == BluetoothGatt.GATT_SUCCESS);
168 } else if (desc.getValue() == BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) {
169 handleUnsubscribeComplete(
170 connId, svcIdBytes, charIdBytes, status == BluetoothGatt.GATT_SUCCESS);
172 Log.d(TAG, "Unexpected onDescriptorWrite().");
177 public void onDescriptorRead(
178 BluetoothGatt gatt, BluetoothGattDescriptor desc, int status) {}
182 public static AndroidChipStack getInstance() {
186 public BluetoothGattCallback getCallback() {
187 return mGattCallback;
190 public synchronized ChipDeviceController getConnection(int connId) {
191 int connIndex = connId - 1;
192 if (connIndex >= 0 && connIndex < mConnections.size()) {
193 return mConnections.get(connIndex);
195 Log.e(TAG, "Unknown connId " + connId);
200 public synchronized int getConnId(BluetoothGatt gatt) {
201 // Find callback given gatt
203 while (connIndex < mConnections.size()) {
204 ChipDeviceController deviceController = mConnections.get(connIndex);
205 if (deviceController != null) {
206 if (gatt == deviceController.getBluetoothGatt()) {
207 return connIndex + 1;
215 // Returns connId, a 1's based version of the index.
216 public synchronized int addConnection(ChipDeviceController connObj) {
218 while (connIndex < mConnections.size()) {
219 if (mConnections.get(connIndex) == null) {
220 mConnections.set(connIndex, connObj);
221 return connIndex + 1;
225 mConnections.add(connIndex, connObj);
226 return connIndex + 1;
229 public synchronized ChipDeviceController removeConnection(int connId) {
230 int connIndex = connId - 1;
231 if (connIndex >= 0 && connIndex < mConnections.size()) {
232 // Set to null, rather than remove, so that other indexes are unchanged.
233 return mConnections.set(connIndex, null);
235 Log.e(TAG, "Trying to remove unknown connId " + connId);
240 public static void onNotifyChipConnectionClosed(int connId) {
241 ChipDeviceController deviceController = AndroidChipStack.getInstance().getConnection(connId);
242 deviceController.onNotifyChipConnectionClosed(connId);
245 public static boolean onSendCharacteristic(
246 int connId, byte[] svcId, byte[] charId, byte[] characteristicData) {
247 ChipDeviceController deviceController = AndroidChipStack.getInstance().getConnection(connId);
248 BluetoothGatt bluetoothGatt = deviceController.getBluetoothGatt();
249 if (bluetoothGatt == null) {
253 UUID svcUUID = convertBytesToUUID(svcId);
254 BluetoothGattService sendSvc = bluetoothGatt.getService(svcUUID);
255 if (sendSvc == null) {
256 Log.e(TAG, "Bad service");
260 UUID charUUID = convertBytesToUUID(charId);
261 BluetoothGattCharacteristic sendChar = sendSvc.getCharacteristic(charUUID);
262 if (!sendChar.setValue(characteristicData)) {
263 Log.e(TAG, "Failed to set characteristic");
267 // Request acknowledgement (use ATT Write Request).
268 sendChar.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
270 if (!bluetoothGatt.writeCharacteristic(sendChar)) {
271 Log.e(TAG, "Failed writing char");
277 public static boolean onSubscribeCharacteristic(int connId, byte[] svcId, byte[] charId) {
278 ChipDeviceController deviceController = AndroidChipStack.getInstance().getConnection(connId);
279 BluetoothGatt bluetoothGatt = deviceController.getBluetoothGatt();
280 if (bluetoothGatt == null) {
284 UUID svcUUID = convertBytesToUUID(svcId);
285 BluetoothGattService subscribeSvc = bluetoothGatt.getService(svcUUID);
286 if (subscribeSvc == null) {
287 Log.e(TAG, "Bad service");
291 UUID charUUID = convertBytesToUUID(charId);
292 BluetoothGattCharacteristic subscribeChar = subscribeSvc.getCharacteristic(charUUID);
293 if (subscribeChar == null) {
294 Log.e(TAG, "Bad characteristic");
298 if (!bluetoothGatt.setCharacteristicNotification(subscribeChar, true)) {
299 Log.e(TAG, "Failed to subscribe to characteristic.");
303 BluetoothGattDescriptor descriptor =
304 subscribeChar.getDescriptor(UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
305 descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
306 if (!bluetoothGatt.writeDescriptor(descriptor)) {
307 Log.e(TAG, "writeDescriptor failed");
313 public static boolean onUnsubscribeCharacteristic(int connId, byte[] svcId, byte[] charId) {
314 ChipDeviceController deviceController = AndroidChipStack.getInstance().getConnection(connId);
315 BluetoothGatt bluetoothGatt = deviceController.getBluetoothGatt();
316 if (bluetoothGatt == null) {
320 UUID svcUUID = convertBytesToUUID(svcId);
321 BluetoothGattService subscribeSvc = bluetoothGatt.getService(svcUUID);
322 if (subscribeSvc == null) {
323 Log.e(TAG, "Bad service");
327 UUID charUUID = convertBytesToUUID(charId);
328 BluetoothGattCharacteristic subscribeChar = subscribeSvc.getCharacteristic(charUUID);
329 if (subscribeChar == null) {
330 Log.e(TAG, "Bad characteristic");
334 if (!bluetoothGatt.setCharacteristicNotification(subscribeChar, false)) {
335 Log.e(TAG, "Failed to unsubscribe to characteristic.");
339 BluetoothGattDescriptor descriptor =
340 subscribeChar.getDescriptor(UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
341 descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
342 if (!bluetoothGatt.writeDescriptor(descriptor)) {
343 Log.e(TAG, "writeDescriptor failed");
349 public static boolean onCloseConnection(int connId) {
350 ChipDeviceController deviceController = AndroidChipStack.getInstance().getConnection(connId);
351 deviceController.onCloseBleComplete(connId);
355 // onGetMTU returns the desired MTU for the BLE connection.
356 // In most cases, a value of 0 is used to indicate no preference.
357 // On some devices, we override to use the minimum MTU to work around device bugs.
358 public static int onGetMTU(int connId) {
360 Log.d(TAG, "Android Manufacturer: (" + Build.MANUFACTURER + ")");
361 Log.d(TAG, "Android Model: (" + Build.MODEL + ")");
363 if (BleMtuDenylist.BLE_MTU_DENYLISTED) {
364 mtu = BleMtuDenylist.BLE_MTU_FALLBACK;
365 Log.e(TAG, "Detected Manufacturer/Model with MTU incompatibiility. Reporting MTU: " + mtu);
370 // ----- Private Members -----
373 System.loadLibrary("CHIPController");
376 private native void handleWriteConfirmation(
377 int connId, byte[] svcId, byte[] charId, boolean success);
379 private native void handleIndicationReceived(
380 int connId, byte[] svcId, byte[] charId, byte[] data);
382 private native void handleSubscribeComplete(
383 int connId, byte[] svcId, byte[] charId, boolean success);
385 private native void handleUnsubscribeComplete(
386 int connId, byte[] svcId, byte[] charId, boolean success);
388 private native void handleConnectionError(int connId);
390 // CLIENT_CHARACTERISTIC_CONFIG is the well-known UUID of the client characteristic descriptor
391 // that has the flags for enabling and disabling notifications and indications.
392 // c.f. https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-attribute-profile
393 private static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
395 private static byte[] convertUUIDToBytes(UUID uuid) {
396 byte[] idBytes = new byte[16];
398 idBits = uuid.getLeastSignificantBits();
400 for (int i = 0; i < 8; i++) {
401 idBytes[15 - i] = (byte) (idBits & 0xff);
402 idBits = idBits >> 8;
405 idBits = uuid.getMostSignificantBits();
406 for (int i = 0; i < 8; i++) {
407 idBytes[7 - i] = (byte) (idBits & 0xff);
408 idBits = idBits >> 8;
414 private static UUID convertBytesToUUID(byte[] id) {
415 long mostSigBits = 0;
416 long leastSigBits = 0;
418 if (id.length == 16) {
419 for (int i = 0; i < 8; i++) {
420 mostSigBits = (mostSigBits << 8) | (0xff & id[i]);
423 for (int i = 0; i < 8; i++) {
424 leastSigBits = (leastSigBits << 8) | (0xff & id[i + 8]);
428 return new UUID(mostSigBits, leastSigBits);