Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 099105b5 authored by Grzegorz Kołodziejczyk's avatar Grzegorz Kołodziejczyk
Browse files

tbs: Distinguish stored CCC value for every characteristic and device

This CL introduces cache storage for CCC values and notification
handling with distinguish for every characteristic and device.

Tag: #feature
Bug: 270317043
Test: atest GoogleBluetoothInstrumentationTests
Change-Id: I5d651266eb01ec72b64ece850cc03bd2f769095a
parent 289d3327
Loading
Loading
Loading
Loading
+55 −27
Original line number Diff line number Diff line
@@ -40,6 +40,8 @@ import com.android.bluetooth.btservice.AdapterService;
import com.android.internal.annotations.VisibleForTesting;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -144,11 +146,11 @@ public class TbsGatt {
    private final GattCharacteristic mCallFriendlyNameCharacteristic;
    private boolean mSilentMode = false;
    private Map<BluetoothDevice, Integer> mStatusFlagValue = new HashMap<>();
    private List<BluetoothDevice> mSubscribers = new ArrayList<>();
    private BluetoothGattServerProxy mBluetoothGattServer;
    private Handler mHandler;
    private Callback mCallback;
    private AdapterService mAdapterService;
    private HashMap<BluetoothDevice, HashMap<UUID, Short>> mCccDescriptorValues;

    public static abstract class Callback {

@@ -236,6 +238,7 @@ public class TbsGatt {
    public boolean init(int ccid, String uci, List<String> uriSchemes,
            boolean isLocalHoldOpcodeSupported, boolean isJoinOpcodeSupported, String providerName,
            int technology, Callback callback) {
        mCccDescriptorValues = new HashMap<>();
        mBearerProviderNameCharacteristic.setValue(providerName);
        mBearerTechnologyCharacteristic.setValue(new byte[] {(byte) (technology & 0xFF)});
        mBearerUciCharacteristic.setValue(uci);
@@ -333,28 +336,51 @@ public class TbsGatt {
        }
    }

    /** Class that handles GATT characteristic notifications */
    private class BluetoothGattCharacteristicNotifier {
        public int setSubscriptionConfiguration(BluetoothDevice device, byte[] configuration) {
            if (Arrays.equals(configuration, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
                mSubscribers.remove(device);
            } else if (!isSubscribed(device)) {
                mSubscribers.add(device);
    @VisibleForTesting
    void setCcc(BluetoothDevice device, UUID charUuid, byte[] value) {
        HashMap<UUID, Short> characteristicCcc = mCccDescriptorValues.get(device);
        if (characteristicCcc == null) {
            characteristicCcc = new HashMap<>();
            mCccDescriptorValues.put(device, characteristicCcc);
        }

            return BluetoothGatt.GATT_SUCCESS;
        characteristicCcc.put(charUuid,
                ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getShort());

        Log.d(TAG, "setCcc, device: " + device.getAddress() + ", UUID: " + charUuid + ", value: "
                + characteristicCcc.get(charUuid));
    }

        public byte[] getSubscriptionConfiguration(BluetoothDevice device) {
            if (isSubscribed(device)) {
                return BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
    private byte[] getCccBytes(BluetoothDevice device, UUID charUuid) {
        Map<UUID, Short> characteristicCcc = mCccDescriptorValues.get(device);
        if (characteristicCcc != null) {
            ByteBuffer bb = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.LITTLE_ENDIAN);
            Short ccc = characteristicCcc.get(charUuid);
            if (ccc != null) {
                bb.putShort(characteristicCcc.get(charUuid));
                return bb.array();
            }
        }

        return BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
    }

        public boolean isSubscribed(BluetoothDevice device) {
            return mSubscribers.contains(device);
    /** Class that handles GATT characteristic notifications */
    private class BluetoothGattCharacteristicNotifier {
        public int setSubscriptionConfiguration(BluetoothDevice device, UUID uuid,
                byte[] configuration) {
            setCcc(device, uuid, configuration);

            return BluetoothGatt.GATT_SUCCESS;
        }

        public byte[] getSubscriptionConfiguration(BluetoothDevice device, UUID uuid) {
            return getCccBytes(device, uuid);
        }

        public boolean isSubscribed(BluetoothDevice device, UUID uuid) {
            return Arrays.equals(getCccBytes(device, uuid),
                    BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        }

        private void notifyCharacteristicChanged(BluetoothDevice device,
@@ -374,20 +400,20 @@ public class TbsGatt {

        public void notifyWithValue(BluetoothDevice device,
                BluetoothGattCharacteristic characteristic, byte[] value) {
            if (isSubscribed(device)) {
            if (isSubscribed(device, characteristic.getUuid())) {
                notifyCharacteristicChanged(device, characteristic, value);
            }
        }

        public void notify(BluetoothDevice device, BluetoothGattCharacteristic characteristic) {
            if (isSubscribed(device)) {
            if (isSubscribed(device, characteristic.getUuid())) {
                notifyCharacteristicChanged(device, characteristic);
            }
        }

        public void notifyAll(BluetoothGattCharacteristic characteristic) {
            for (BluetoothDevice device : mSubscribers) {
                notifyCharacteristicChanged(device, characteristic);
            for (BluetoothDevice device : mCccDescriptorValues.keySet()) {
                notify(device, characteristic);
            }
        }
    }
@@ -407,12 +433,13 @@ public class TbsGatt {
            }
        }

        public byte[] getSubscriptionConfiguration(BluetoothDevice device) {
            return mNotifier.getSubscriptionConfiguration(device);
        public byte[] getSubscriptionConfiguration(BluetoothDevice device, UUID uuid) {
            return mNotifier.getSubscriptionConfiguration(device, uuid);
        }

        public int setSubscriptionConfiguration(BluetoothDevice device, byte[] configuration) {
            return mNotifier.setSubscriptionConfiguration(device, configuration);
        public int setSubscriptionConfiguration(BluetoothDevice device, UUID uuid,
                byte[] configuration) {
            return mNotifier.setSubscriptionConfiguration(device, uuid, configuration);
        }

        private boolean isNotifiable() {
@@ -528,7 +555,8 @@ public class TbsGatt {

        public byte[] getValue(BluetoothDevice device) {
            GattCharacteristic characteristic = (GattCharacteristic) getCharacteristic();
            byte value[] = characteristic.getSubscriptionConfiguration(device);
            byte[] value = characteristic.getSubscriptionConfiguration(device,
                    characteristic.getUuid());
            if (value == null) {
                return BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
            }
@@ -561,7 +589,8 @@ public class TbsGatt {
                Log.e(TAG, "Not handled CCC value: " + Arrays.toString(value));
            }

            return characteristic.setSubscriptionConfiguration(device, value);
            return characteristic.setSubscriptionConfiguration(device, characteristic.getUuid(),
                    value);
        }
    }

@@ -657,7 +686,7 @@ public class TbsGatt {

    private boolean updateStatusFlagsSilentMode(boolean set) {
        mSilentMode = set;
        for (BluetoothDevice device: mSubscribers) {
        for (BluetoothDevice device: mCccDescriptorValues.keySet()) {
            boolean entryExist = mStatusFlagValue.containsKey(device);
            if (entryExist
                    && (((mStatusFlagValue.get(device)
@@ -845,7 +874,6 @@ public class TbsGatt {
                }

                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                mSubscribers.add(device);
            }
        }
    }
+128 −41
Original line number Diff line number Diff line
@@ -172,7 +172,7 @@ public class TbsGattTest {
    }

    private void verifySetValue(BluetoothGattCharacteristic characteristic, Object value,
            boolean shouldNotify) {
            boolean shouldNotify, BluetoothDevice device, boolean clearGattMock) {
        boolean notifyWithValue = false;

        if (characteristic.getUuid().equals(TbsGatt.UUID_BEARER_PROVIDER_NAME)) {
@@ -185,13 +185,7 @@ public class TbsGattTest {
            Assert.assertEquals((String) value, characteristic.getStringValue(0));

        } else if (characteristic.getUuid().equals(TbsGatt.UUID_BEARER_TECHNOLOGY)) {
            boolean valueChanged = !Objects.equals(characteristic
                    .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0), (Integer) value);
            if (valueChanged) {
            Assert.assertTrue(mTbsGatt.setBearerTechnology((Integer) value));
            } else {
                Assert.assertFalse(mTbsGatt.setBearerTechnology((Integer) value));
            }
            Assert.assertEquals((Integer) value,
                    characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0));

@@ -212,9 +206,9 @@ public class TbsGattTest {
            switch (flagStatePair.first) {
                case TbsGatt.STATUS_FLAG_INBAND_RINGTONE_ENABLED:
                    if (flagStatePair.second) {
                        Assert.assertTrue(mTbsGatt.setInbandRingtoneFlag(mFirstDevice));
                        Assert.assertTrue(mTbsGatt.setInbandRingtoneFlag(device));
                    } else {
                        Assert.assertTrue(mTbsGatt.clearInbandRingtoneFlag(mFirstDevice));
                        Assert.assertTrue(mTbsGatt.clearInbandRingtoneFlag(device));
                    }
                    break;

@@ -285,24 +279,26 @@ public class TbsGattTest {

        if (shouldNotify) {
                if (notifyWithValue) {
                        verify(mMockGattServer).notifyCharacteristicChanged(eq(mFirstDevice),
                        verify(mMockGattServer).notifyCharacteristicChanged(eq(device),
                                eq(characteristic), eq(false), any());
                } else {
                        verify(mMockGattServer).notifyCharacteristicChanged(eq(mFirstDevice),
                        verify(mMockGattServer).notifyCharacteristicChanged(eq(device),
                                eq(characteristic), eq(false));
                }
        } else {
                if (notifyWithValue) {
                        verify(mMockGattServer, times(0)).notifyCharacteristicChanged(any(), any(),
                                anyBoolean(), any());
                        verify(mMockGattServer, times(0)).notifyCharacteristicChanged(eq(device),
                                eq(characteristic), anyBoolean(), any());
                } else {
                        verify(mMockGattServer, times(0)).notifyCharacteristicChanged(any(), any(),
                                anyBoolean());
                        verify(mMockGattServer, times(0)).notifyCharacteristicChanged(eq(device),
                                eq(characteristic), anyBoolean());
                }
        }

        if (clearGattMock) {
            reset(mMockGattServer);
        }
    }

    @Test
    public void testSetBearerProviderName() {
@@ -312,11 +308,11 @@ public class TbsGattTest {

        // Check with notifications enabled
        configureNotifications(mFirstDevice, characteristic, true);
        verifySetValue(characteristic, "providerName2", true);
        verifySetValue(characteristic, "providerName2", true, mFirstDevice, true);

        // Check with notifications disabled
        configureNotifications(mFirstDevice, characteristic, false);
        verifySetValue(characteristic, "providerName3", false);
        verifySetValue(characteristic, "providerName3", false, mFirstDevice, true);
    }

    @Test
@@ -327,11 +323,11 @@ public class TbsGattTest {

        // Check with notifications enabled
        configureNotifications(mFirstDevice, characteristic, true);
        verifySetValue(characteristic, 0x04, true);
        verifySetValue(characteristic, 0x04, true, mFirstDevice, true);

        // Check with notifications disabled
        configureNotifications(mFirstDevice, characteristic, false);
        verifySetValue(characteristic, 0x05, false);
        verifySetValue(characteristic, 0x05, false, mFirstDevice, true);
    }

    @Test
@@ -342,11 +338,13 @@ public class TbsGattTest {

        // Check with notifications enabled
        configureNotifications(mFirstDevice, characteristic, true);
        verifySetValue(characteristic, new ArrayList<>(Arrays.asList("uri2", "uri3")), true);
        verifySetValue(characteristic, new ArrayList<>(Arrays.asList("uri2", "uri3")), true,
                mFirstDevice, true);

        // Check with notifications disabled
        configureNotifications(mFirstDevice, characteristic, false);
        verifySetValue(characteristic, new ArrayList<>(Arrays.asList("uri4", "uri5")), false);
        verifySetValue(characteristic, new ArrayList<>(Arrays.asList("uri4", "uri5")), false,
                mFirstDevice, true);
    }

    @Test
@@ -372,7 +370,8 @@ public class TbsGattTest {
                // URI: tel:123456789
        };
        verifySetValue(characteristic,
                new Pair<Map<Integer, TbsCall>, byte[]>(callsMap, packetExpected), true);
                new Pair<Map<Integer, TbsCall>, byte[]>(callsMap, packetExpected), true,
                mFirstDevice, true);

        // Check with notifications disabled
        configureNotifications(mFirstDevice, characteristic, false);
@@ -399,7 +398,8 @@ public class TbsGattTest {
                // URI: tel:987654321
        };
        verifySetValue(characteristic,
                new Pair<Map<Integer, TbsCall>, byte[]>(callsMap, packetExpected), false);
                new Pair<Map<Integer, TbsCall>, byte[]>(callsMap, packetExpected), false,
                mFirstDevice, true);
    }

    @Test
@@ -411,14 +411,16 @@ public class TbsGattTest {
        configureNotifications(mFirstDevice, characteristic, true);
        verifySetValue(characteristic,
                new Pair<Integer, Boolean>(TbsGatt.STATUS_FLAG_INBAND_RINGTONE_ENABLED, true),
                true);
                true, mFirstDevice, true);
        verifySetValue(characteristic,
                new Pair<Integer, Boolean>(TbsGatt.STATUS_FLAG_SILENT_MODE_ENABLED, true), true);
                new Pair<Integer, Boolean>(TbsGatt.STATUS_FLAG_SILENT_MODE_ENABLED, true), true,
                mFirstDevice, true);

        // Check with notifications disabled
        configureNotifications(mFirstDevice, characteristic, false);
        verifySetValue(characteristic,
                new Pair<Integer, Boolean>(TbsGatt.STATUS_FLAG_SILENT_MODE_ENABLED, false), false);
                new Pair<Integer, Boolean>(TbsGatt.STATUS_FLAG_SILENT_MODE_ENABLED, false), false,
                mFirstDevice, true);
    }

    @Test
@@ -437,7 +439,8 @@ public class TbsGattTest {
        callsMap.put(0x0A, TbsCall.create(
                new BluetoothLeCall(UUID.randomUUID(), "tel:123456789", "John Doe", 0x03, 0x00)));
        verifySetValue(characteristic,
                new Pair<Map<Integer, TbsCall>, byte[]>(callsMap, packetExpected), true);
                new Pair<Map<Integer, TbsCall>, byte[]>(callsMap, packetExpected), true,
                mFirstDevice, true);

        // Check with notifications disabled
        configureNotifications(mFirstDevice, characteristic, false);
@@ -453,7 +456,8 @@ public class TbsGattTest {
        callsMap.put(0x0B, TbsCall.create(
                new BluetoothLeCall(UUID.randomUUID(), "tel:987654321", "Kate", 0x04, 0x00)));
        verifySetValue(characteristic,
                new Pair<Map<Integer, TbsCall>, byte[]>(callsMap, packetExpected), false);
                new Pair<Map<Integer, TbsCall>, byte[]>(callsMap, packetExpected), false,
                mFirstDevice, true);
    }

    @Test
@@ -495,15 +499,18 @@ public class TbsGattTest {
                getCharacteristic(TbsGatt.UUID_TERMINATION_REASON);

        // Check with no CCC configured
        verifySetValue(characteristic, new Pair<Integer, Integer>(0x0A, 0x01), false);
        verifySetValue(characteristic, new Pair<Integer, Integer>(0x0A, 0x01), false, mFirstDevice,
                true);

        // Check with notifications enabled
        configureNotifications(mFirstDevice, characteristic, true);
        verifySetValue(characteristic, new Pair<Integer, Integer>(0x0B, 0x02), true);
        verifySetValue(characteristic, new Pair<Integer, Integer>(0x0B, 0x02), true, mFirstDevice,
                true);

        // Check with notifications disabled
        configureNotifications(mFirstDevice, characteristic, false);
        verifySetValue(characteristic, new Pair<Integer, Integer>(0x0C, 0x02), false);
        verifySetValue(characteristic, new Pair<Integer, Integer>(0x0C, 0x02), false, mFirstDevice,
                true);
    }

    @Test
@@ -512,18 +519,21 @@ public class TbsGattTest {
        BluetoothGattCharacteristic characteristic = getCharacteristic(TbsGatt.UUID_INCOMING_CALL);

        // Check with no CCC configured
        verifySetValue(characteristic, new Pair<Integer, String>(0x0A, "tel:123456789"), false);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0A, "tel:123456789"), false,
                mFirstDevice, true);

        // Check with notifications enabled
        configureNotifications(mFirstDevice, characteristic, true);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0A, "tel:987654321"), true);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0A, "tel:987654321"), true,
                mFirstDevice, true);

        // No incoming call (should not send any notification)
        verifySetValue(characteristic, null, false);
        verifySetValue(characteristic, null, false, mFirstDevice, true);

        // Check with notifications disabled
        configureNotifications(mFirstDevice, characteristic, false);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0A, "tel:123456789"), false);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0A, "tel:123456789"), false,
                mFirstDevice, true);
    }

    @Test
@@ -533,18 +543,21 @@ public class TbsGattTest {
                getCharacteristic(TbsGatt.UUID_CALL_FRIENDLY_NAME);

        // Check with no CCC configured
        verifySetValue(characteristic, new Pair<Integer, String>(0x0A, "PersonA"), false);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0A, "PersonA"), false,
                mFirstDevice, true);

        // Check with notifications enabled
        configureNotifications(mFirstDevice, characteristic, true);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0B, "PersonB"), true);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0B, "PersonB"), true,
                mFirstDevice, true);

        // Clear freindly name (should not send any notification)
        verifySetValue(characteristic, null, false);
        verifySetValue(characteristic, null, false, mFirstDevice, true);

        // Check with notifications disabled
        configureNotifications(mFirstDevice, characteristic, false);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0C, "PersonC"), false);
        verifySetValue(characteristic, new Pair<Integer, String>(0x0C, "PersonC"), false,
                mFirstDevice, true);
    }

    @Test
@@ -729,4 +742,78 @@ public class TbsGattTest {
                eq(BluetoothGatt.GATT_SUCCESS), eq(0),
                eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));
    }

    @Test
    public void testMultipleClientCharacteristicConfiguration() {
        prepareDefaultService();

        BluetoothGattCharacteristic characteristic =
                getCharacteristic(TbsGatt.UUID_BEARER_TECHNOLOGY);
        BluetoothGattDescriptor descriptor =
                characteristic.getDescriptor(TbsGatt.UUID_CLIENT_CHARACTERISTIC_CONFIGURATION);

        // Check with no configuration
        mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor);
        verify(mMockGattServer).sendResponse(eq(mFirstDevice), eq(1),
                eq(BluetoothGatt.GATT_SUCCESS), eq(0),
                eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));

        mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mSecondDevice, 1, 0, descriptor);
        verify(mMockGattServer).sendResponse(eq(mSecondDevice), eq(1),
                eq(BluetoothGatt.GATT_SUCCESS), eq(0),
                eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));
        reset(mMockGattServer);

        // Check with notifications enabled for first device
        configureNotifications(mFirstDevice, characteristic, true);
        verifySetValue(characteristic, 4, true, mFirstDevice, true);
        mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor);
        verify(mMockGattServer).sendResponse(eq(mFirstDevice), eq(1),
                eq(BluetoothGatt.GATT_SUCCESS), eq(0),
                eq(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
        reset(mMockGattServer);

        // Check if second device is still not subscribed for notifications and will not get it
        verifySetValue(characteristic, 5, false, mSecondDevice, false);
        verify(mMockGattServer).notifyCharacteristicChanged(eq(mFirstDevice),
                                eq(characteristic), eq(false));
        mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mSecondDevice, 1, 0, descriptor);
        verify(mMockGattServer).sendResponse(eq(mSecondDevice), eq(1),
                eq(BluetoothGatt.GATT_SUCCESS), eq(0),
                eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));
        reset(mMockGattServer);

        // Check with notifications enabled for first and second device
        configureNotifications(mSecondDevice, characteristic, true);
        verifySetValue(characteristic, 6, true, mSecondDevice, false);
        verify(mMockGattServer).notifyCharacteristicChanged(eq(mFirstDevice),
                                eq(characteristic), eq(false));
        mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mSecondDevice, 1, 0, descriptor);
        verify(mMockGattServer).sendResponse(eq(mSecondDevice), eq(1),
                eq(BluetoothGatt.GATT_SUCCESS), eq(0),
                eq(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
        reset(mMockGattServer);

        // Disable notification for first device, check if second will get notification
        configureNotifications(mFirstDevice, characteristic, false);
        verifySetValue(characteristic, 7, false, mFirstDevice, false);
        verify(mMockGattServer).notifyCharacteristicChanged(eq(mSecondDevice),
                                eq(characteristic), eq(false));
        mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mFirstDevice, 1, 0, descriptor);
        verify(mMockGattServer).sendResponse(eq(mFirstDevice), eq(1),
                eq(BluetoothGatt.GATT_SUCCESS), eq(0),
                eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));
        reset(mMockGattServer);

        // Check with notifications disabled of both device
        configureNotifications(mSecondDevice, characteristic, false);
        verifySetValue(characteristic, 4, false, mFirstDevice, false);
        verify(mMockGattServer, times(0)).notifyCharacteristicChanged(eq(mSecondDevice),
                                eq(characteristic), eq(false));
        mTbsGatt.mGattServerCallback.onDescriptorReadRequest(mSecondDevice, 1, 0, descriptor);
        verify(mMockGattServer).sendResponse(eq(mSecondDevice), eq(1),
                eq(BluetoothGatt.GATT_SUCCESS), eq(0),
                eq(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE));

    }
}