Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / src / controller / java / src / chip / devicecontroller / AndroidChipStack.java
1 /*
2  *   Copyright (c) 2020 Project CHIP Authors
3  *   All rights reserved.
4  *
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
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  */
18 package chip.devicecontroller;
19
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;
31
32 /*
33  * AndroidChipStack - a singleton object shared between all instances of
34  * ChipDeviceController.
35  *
36  * Holds list of active connections with mapping to the associated
37  * connection object.
38  */
39 public final class AndroidChipStack {
40
41   private static final String TAG = AndroidChipStack.class.getSimpleName();
42   public static final int INITIAL_CONNECTIONS = 4;
43
44   private static class BleMtuDenylist {
45     /**
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.
48      */
49     static final boolean BLE_MTU_DENYLISTED;
50
51     /**
52      * If {@link #BLE_MTU_DENYLISTED} is true, then this is the fallback MTU to use for this device
53      */
54     static final int BLE_MTU_FALLBACK = 23;
55
56     static {
57       if ("OnePlus".equals(Build.MANUFACTURER)) {
58         BLE_MTU_DENYLISTED = "ONE A2005".equals(Build.MODEL) ? true : false;
59       } else if ("motorola".equals(Build.MANUFACTURER)) {
60         BLE_MTU_DENYLISTED =
61             "XT1575".equals(Build.MODEL) || "XT1585".equals(Build.MODEL) ? true : false;
62       } else {
63         BLE_MTU_DENYLISTED = false;
64       }
65     }
66   }
67
68   /* Singleton instance of this class */
69   private static final AndroidChipStack sInstance = new AndroidChipStack();
70
71   /* Mapping of connections to connection objects */
72   private final List<ChipDeviceController> mConnections;
73
74   private BluetoothGattCallback mGattCallback;
75
76   private AndroidChipStack() {
77     mConnections = new ArrayList<ChipDeviceController>(INITIAL_CONNECTIONS);
78     mGattCallback =
79         new BluetoothGattCallback() {
80           @Override
81           public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
82             int connId = 0;
83
84             if (newState == BluetoothProfile.STATE_DISCONNECTED) {
85               connId = getConnId(gatt);
86               if (connId > 0) {
87                 Log.d(TAG, "onConnectionStateChange Disconnected");
88                 handleConnectionError(connId);
89               } else {
90                 Log.e(TAG, "onConnectionStateChange disconnected: no active connection");
91               }
92             }
93           }
94
95           @Override
96           public void onServicesDiscovered(BluetoothGatt gatt, int status) {}
97
98           @Override
99           public void onCharacteristicRead(
100               BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
101
102           @Override
103           public void onCharacteristicWrite(
104               BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
105             byte[] svcIdBytes = convertUUIDToBytes(characteristic.getService().getUuid());
106             byte[] charIdBytes = convertUUIDToBytes(characteristic.getUuid());
107
108             if (status != BluetoothGatt.GATT_SUCCESS) {
109               Log.e(
110                   TAG,
111                   "onCharacteristicWrite for "
112                       + characteristic.getUuid().toString()
113                       + " failed with status: "
114                       + status);
115               return;
116             }
117
118             int connId = getConnId(gatt);
119             if (connId > 0) {
120               handleWriteConfirmation(
121                   connId, svcIdBytes, charIdBytes, status == BluetoothGatt.GATT_SUCCESS);
122             } else {
123               Log.e(TAG, "onCharacteristicWrite no active connection");
124               return;
125             }
126           }
127
128           @Override
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);
134             if (connId > 0) {
135               handleIndicationReceived(connId, svcIdBytes, charIdBytes, characteristic.getValue());
136             } else {
137               Log.e(TAG, "onCharacteristicChanged no active connection");
138               return;
139             }
140           }
141
142           @Override
143           public void onDescriptorWrite(
144               BluetoothGatt gatt, BluetoothGattDescriptor desc, int status) {
145             BluetoothGattCharacteristic characteristic = desc.getCharacteristic();
146
147             byte[] svcIdBytes = convertUUIDToBytes(characteristic.getService().getUuid());
148             byte[] charIdBytes = convertUUIDToBytes(characteristic.getUuid());
149
150             if (status != BluetoothGatt.GATT_SUCCESS) {
151               Log.e(
152                   TAG,
153                   "onDescriptorWrite for "
154                       + desc.getUuid().toString()
155                       + " failed with status: "
156                       + status);
157             }
158
159             int connId = getConnId(gatt);
160             if (connId == 0) {
161               Log.e(TAG, "onDescriptorWrite no active connection");
162               return;
163             }
164
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);
171             } else {
172               Log.d(TAG, "Unexpected onDescriptorWrite().");
173             }
174           }
175
176           @Override
177           public void onDescriptorRead(
178               BluetoothGatt gatt, BluetoothGattDescriptor desc, int status) {}
179         };
180   }
181
182   public static AndroidChipStack getInstance() {
183     return sInstance;
184   }
185
186   public BluetoothGattCallback getCallback() {
187     return mGattCallback;
188   }
189
190   public synchronized ChipDeviceController getConnection(int connId) {
191     int connIndex = connId - 1;
192     if (connIndex >= 0 && connIndex < mConnections.size()) {
193       return mConnections.get(connIndex);
194     } else {
195       Log.e(TAG, "Unknown connId " + connId);
196       return null;
197     }
198   }
199
200   public synchronized int getConnId(BluetoothGatt gatt) {
201     // Find callback given gatt
202     int connIndex = 0;
203     while (connIndex < mConnections.size()) {
204       ChipDeviceController deviceController = mConnections.get(connIndex);
205       if (deviceController != null) {
206         if (gatt == deviceController.getBluetoothGatt()) {
207           return connIndex + 1;
208         }
209       }
210       connIndex++;
211     }
212     return 0;
213   }
214
215   // Returns connId, a 1's based version of the index.
216   public synchronized int addConnection(ChipDeviceController connObj) {
217     int connIndex = 0;
218     while (connIndex < mConnections.size()) {
219       if (mConnections.get(connIndex) == null) {
220         mConnections.set(connIndex, connObj);
221         return connIndex + 1;
222       }
223       connIndex++;
224     }
225     mConnections.add(connIndex, connObj);
226     return connIndex + 1;
227   }
228
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);
234     } else {
235       Log.e(TAG, "Trying to remove unknown connId " + connId);
236       return null;
237     }
238   }
239
240   public static void onNotifyChipConnectionClosed(int connId) {
241     ChipDeviceController deviceController = AndroidChipStack.getInstance().getConnection(connId);
242     deviceController.onNotifyChipConnectionClosed(connId);
243   }
244
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) {
250       return false;
251     }
252
253     UUID svcUUID = convertBytesToUUID(svcId);
254     BluetoothGattService sendSvc = bluetoothGatt.getService(svcUUID);
255     if (sendSvc == null) {
256       Log.e(TAG, "Bad service");
257       return false;
258     }
259
260     UUID charUUID = convertBytesToUUID(charId);
261     BluetoothGattCharacteristic sendChar = sendSvc.getCharacteristic(charUUID);
262     if (!sendChar.setValue(characteristicData)) {
263       Log.e(TAG, "Failed to set characteristic");
264       return false;
265     }
266
267     // Request acknowledgement (use ATT Write Request).
268     sendChar.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
269
270     if (!bluetoothGatt.writeCharacteristic(sendChar)) {
271       Log.e(TAG, "Failed writing char");
272       return false;
273     }
274     return true;
275   }
276
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) {
281       return false;
282     }
283
284     UUID svcUUID = convertBytesToUUID(svcId);
285     BluetoothGattService subscribeSvc = bluetoothGatt.getService(svcUUID);
286     if (subscribeSvc == null) {
287       Log.e(TAG, "Bad service");
288       return false;
289     }
290
291     UUID charUUID = convertBytesToUUID(charId);
292     BluetoothGattCharacteristic subscribeChar = subscribeSvc.getCharacteristic(charUUID);
293     if (subscribeChar == null) {
294       Log.e(TAG, "Bad characteristic");
295       return false;
296     }
297
298     if (!bluetoothGatt.setCharacteristicNotification(subscribeChar, true)) {
299       Log.e(TAG, "Failed to subscribe to characteristic.");
300       return false;
301     }
302
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");
308       return false;
309     }
310     return true;
311   }
312
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) {
317       return false;
318     }
319
320     UUID svcUUID = convertBytesToUUID(svcId);
321     BluetoothGattService subscribeSvc = bluetoothGatt.getService(svcUUID);
322     if (subscribeSvc == null) {
323       Log.e(TAG, "Bad service");
324       return false;
325     }
326
327     UUID charUUID = convertBytesToUUID(charId);
328     BluetoothGattCharacteristic subscribeChar = subscribeSvc.getCharacteristic(charUUID);
329     if (subscribeChar == null) {
330       Log.e(TAG, "Bad characteristic");
331       return false;
332     }
333
334     if (!bluetoothGatt.setCharacteristicNotification(subscribeChar, false)) {
335       Log.e(TAG, "Failed to unsubscribe to characteristic.");
336       return false;
337     }
338
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");
344       return false;
345     }
346     return true;
347   }
348
349   public static boolean onCloseConnection(int connId) {
350     ChipDeviceController deviceController = AndroidChipStack.getInstance().getConnection(connId);
351     deviceController.onCloseBleComplete(connId);
352     return true;
353   }
354
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) {
359     int mtu = 0;
360     Log.d(TAG, "Android Manufacturer: (" + Build.MANUFACTURER + ")");
361     Log.d(TAG, "Android Model: (" + Build.MODEL + ")");
362
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);
366     }
367     return mtu;
368   }
369
370   // ----- Private Members -----
371
372   static {
373     System.loadLibrary("CHIPController");
374   }
375
376   private native void handleWriteConfirmation(
377       int connId, byte[] svcId, byte[] charId, boolean success);
378
379   private native void handleIndicationReceived(
380       int connId, byte[] svcId, byte[] charId, byte[] data);
381
382   private native void handleSubscribeComplete(
383       int connId, byte[] svcId, byte[] charId, boolean success);
384
385   private native void handleUnsubscribeComplete(
386       int connId, byte[] svcId, byte[] charId, boolean success);
387
388   private native void handleConnectionError(int connId);
389
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";
394
395   private static byte[] convertUUIDToBytes(UUID uuid) {
396     byte[] idBytes = new byte[16];
397     long idBits;
398     idBits = uuid.getLeastSignificantBits();
399
400     for (int i = 0; i < 8; i++) {
401       idBytes[15 - i] = (byte) (idBits & 0xff);
402       idBits = idBits >> 8;
403     }
404
405     idBits = uuid.getMostSignificantBits();
406     for (int i = 0; i < 8; i++) {
407       idBytes[7 - i] = (byte) (idBits & 0xff);
408       idBits = idBits >> 8;
409     }
410
411     return idBytes;
412   }
413
414   private static UUID convertBytesToUUID(byte[] id) {
415     long mostSigBits = 0;
416     long leastSigBits = 0;
417
418     if (id.length == 16) {
419       for (int i = 0; i < 8; i++) {
420         mostSigBits = (mostSigBits << 8) | (0xff & id[i]);
421       }
422
423       for (int i = 0; i < 8; i++) {
424         leastSigBits = (leastSigBits << 8) | (0xff & id[i + 8]);
425       }
426     }
427
428     return new UUID(mostSigBits, leastSigBits);
429   }
430 }