com.sogilis.ReactNativeBluetooth.ReactNativeBluetoothModule.java Source code

Java tutorial

Introduction

Here is the source code for com.sogilis.ReactNativeBluetooth.ReactNativeBluetoothModule.java

Source

/**
 * Copyright (c) 2016-present, Sogilis SARL
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sogilis.ReactNativeBluetooth;

import android.bluetooth.*;
import android.bluetooth.le.*;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.ParcelUuid;
import android.util.Base64;
import android.util.Log;
import com.facebook.react.bridge.*;
import com.sogilis.ReactNativeBluetooth.domain.BluetoothException;
import com.sogilis.ReactNativeBluetooth.domain.DeviceCollection;
import com.sogilis.ReactNativeBluetooth.domain.GattCollection;
import com.sogilis.ReactNativeBluetooth.events.BluetoothEvent;
import com.sogilis.ReactNativeBluetooth.events.EventEmitter;

import java.util.*;

import static android.bluetooth.BluetoothGatt.GATT_SUCCESS;
import static android.bluetooth.le.ScanSettings.*;
import static com.sogilis.ReactNativeBluetooth.Constants.MODULE_NAME;
import static com.sogilis.ReactNativeBluetooth.domain.BluetoothHelpers.*;
import static com.sogilis.ReactNativeBluetooth.events.EventBuilders.*;
import static com.sogilis.ReactNativeBluetooth.events.EventNames.*;

public class ReactNativeBluetoothModule extends ReactContextBaseJavaModule {

    @Override
    public String getName() {
        return MODULE_NAME;
    }

    private DeviceCollection discoveredDevices = new DeviceCollection();
    private GattCollection gattCollection = new GattCollection();
    private EventEmitter eventEmitter;

    // States
    private static final String STATE_ENABLED = "enabled";
    private static final String STATE_DISABLED = "disabled";

    private BluetoothActionsLoop bluetoothActionsLoop;

    public ReactNativeBluetoothModule(ReactApplicationContext reactContext) {
        super(reactContext);
        eventEmitter = new EventEmitter(reactContext);

        reactContext.registerReceiver(stateChangeReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));

        reactContext.registerReceiver(pairingStatusChangeReceiver,
                new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));

        bluetoothActionsLoop = new BluetoothActionsLoop();
    }

    private void emit(BluetoothEvent event) {
        eventEmitter.emit(event);
    }

    private void emitGattError(String eventName, int status, BluetoothGatt gatt) {
        String deviceId = deviceId(gatt.getDevice());
        eventEmitter.emitError(eventName, gattStatusString(status), deviceId);
    }

    private void emitGattCharacteristicError(String eventName, int status,
            BluetoothGattCharacteristic characteristic) {
        String charId = characteristic.getUuid().toString();
        eventEmitter.emitError(eventName, gattStatusString(status), charId);
    }

    private void didEnableBluetooth() {
        emit(stateChanged(STATE_ENABLED));
    }

    private void didDisableBluetooth() {
        gattCollection.clear();
        discoveredDevices.clear();
        emit(stateChanged(STATE_DISABLED));
    }

    private BroadcastReceiver stateChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final int newStateCode = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);

            if (newStateCode == BluetoothAdapter.STATE_ON) {
                didEnableBluetooth();

            } else if (newStateCode == BluetoothAdapter.STATE_OFF) {
                didDisableBluetooth();
            }
        }
    };

    @ReactMethod
    public void notifyCurrentState() {
        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) {
            emit(stateChanged(STATE_ENABLED));
        } else {
            emit(stateChanged(STATE_DISABLED));
        }
    }

    ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            if (discoveredDevices.add(result.getDevice())) {
                emit(deviceDiscovered(result.getDevice()));
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            for (ScanResult result : results) {
                if (discoveredDevices.add(result.getDevice())) {
                    emit(deviceDiscovered(result.getDevice()));
                }
            }
        }
    };

    private List<ScanFilter> getScanFiltersFromUUIDStrings(ReadableArray uuidStrings) {
        List<ScanFilter> filters = new ArrayList<>();

        for (int i = 0; i < uuidStrings.size(); i++) {
            UUID uuid = UUID.fromString(uuidStrings.getString(i));
            ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder();
            scanFilterBuilder.setServiceUuid(new ParcelUuid(uuid));
            filters.add(scanFilterBuilder.build());
        }

        return filters;
    }

    @ReactMethod
    public void startScan(final ReadableArray uuidStrings) {
        new BluetoothAction(SCAN_STARTED, eventEmitter) {
            @Override
            public void run() {
                discoveredDevices.clear();
                final BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner();
                final ScanSettings settings = new ScanSettings.Builder().setCallbackType(CALLBACK_TYPE_ALL_MATCHES)
                        .setScanMode(SCAN_MODE_LOW_LATENCY).build();
                final List<ScanFilter> filters = getScanFiltersFromUUIDStrings(uuidStrings);

                scanner.startScan(filters, settings, scanCallback);
                emit(scanStarted());
            }
        }.start();
    }

    @ReactMethod
    public void stopScan() {
        new BluetoothAction(SCAN_STOPPED, eventEmitter) {
            @Override
            public void run() {
                bluetoothAdapter.getBluetoothLeScanner().stopScan(scanCallback);
                emit(scanStopped());
            }
        }.start();
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            BluetoothDevice device = gatt.getDevice();
            String deviceId = deviceId(device);

            Log.d(MODULE_NAME, "#onConnectionStateChange: " + newState + " - status: " + status);

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                if (status == GATT_SUCCESS) {
                    gattCollection.add(gatt);
                    emit(deviceConnected(device));
                } else {
                    emitGattError(DEVICE_CONNECTED, status, gatt);
                }
                bluetoothActionsLoop.actionDone();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                gattCollection.close(deviceId);
                String statusString;
                if (status == GATT_SUCCESS) {
                    emit(deviceDisconnected(device));
                    statusString = DEVICE_DISCONNECTED;
                } else {
                    emitGattError(DEVICE_DISCONNECTED, status, gatt);
                    statusString = DEVICE_DISCONNECTED + " " + gattStatusString(status);
                }
                bluetoothActionsLoop.cancelGattActions(deviceId, statusString);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == GATT_SUCCESS) {
                emit(servicesDiscovered(gatt.getDevice(), gatt.getServices()));
            } else {
                emitGattError(SERVICES_DISCOVERED, status, gatt);
            }
            bluetoothActionsLoop.actionDone();
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
                int status) {
            if (status == GATT_SUCCESS) {
                emit(characteristicRead(gatt.getDevice(), characteristic));
            } else {
                emitGattCharacteristicError(CHARACTERISTIC_READ, status, characteristic);
            }
            bluetoothActionsLoop.actionDone();
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
                int status) {
            if (status == GATT_SUCCESS) {
                emit(characteristicWritten(gatt.getDevice(), characteristic));
            } else {
                emitGattCharacteristicError(CHARACTERISTIC_WRITTEN, status, characteristic);
            }
            bluetoothActionsLoop.actionDone();
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
            bluetoothActionsLoop.actionDone();
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            emit(characteristicNotified(gatt.getDevice(), characteristic));
            bluetoothActionsLoop.actionDone();
        }
    };

    private BroadcastReceiver pairingStatusChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            if (device.getBondState() == BluetoothDevice.BOND_BONDED
                    || device.getBondState() == BluetoothDevice.BOND_NONE) {
                device.connectGatt(getReactApplicationContext(), false, gattCallback);
            }
        }
    };

    @ReactMethod
    public void connect(final ReadableMap deviceMap) {
        final String deviceId = deviceMap.getString("id");

        BluetoothAction connectAction = new BluetoothAction(DEVICE_CONNECTED, deviceId, eventEmitter) {
            @Override
            public void run() throws BluetoothException {
                BluetoothDevice device = discoveredDevices.get(deviceId);
                Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
                if (!bondedDevices.contains(device)) {
                    if (!device.createBond()) {
                        throw new BluetoothException("Error when requesting device pairing");
                    }
                } else {
                    device.connectGatt(getReactApplicationContext(), false, gattCallback);
                }
            }
        };

        bluetoothActionsLoop.addAction(connectAction);
    }

    @ReactMethod
    public void disconnect(final ReadableMap deviceMap) {
        final String deviceId = deviceMap.getString("id");

        BluetoothAction disconnectAction = new BluetoothAction(DEVICE_DISCONNECTED, deviceId, eventEmitter) {
            @Override
            public void run() throws BluetoothException {
                BluetoothGatt gatt = gattCollection.get(deviceId);
                gatt.disconnect();
                bluetoothActionsLoop.actionDone();
            }
        };

        bluetoothActionsLoop.addAction(disconnectAction);
    }

    @ReactMethod
    public void discoverServices(final ReadableMap deviceMap, final ReadableArray serviceIds) {
        final String deviceId = deviceMap.getString("id");

        BluetoothAction discoverServicesAction = new BluetoothAction(SERVICE_DISCOVERY_STARTED, deviceId,
                eventEmitter) {
            @Override
            public void run() throws BluetoothException {
                BluetoothGatt gatt = gattCollection.get(deviceId);

                if (gatt.discoverServices()) {
                    emit(serviceDiscoveryStarted(gatt.getDevice()));
                } else {
                    throw new BluetoothException("Error when requesting services discovery");
                }
            }
        };

        bluetoothActionsLoop.addAction(discoverServicesAction);
    }

    @ReactMethod
    public void discoverCharacteristics(final ReadableMap serviceMap, final ReadableArray characteristicIds) {
        final String deviceId = serviceMap.getString("deviceId");
        final String serviceId = serviceMap.getString("id");

        BluetoothAction discoverAction = new BluetoothAction(CHARACTERISTIC_DISCOVERY_STARTED, deviceId,
                eventEmitter) {
            @Override
            public void run() throws BluetoothException {
                BluetoothDevice device = discoveredDevices.get(deviceId);
                BluetoothGatt gatt = gattCollection.get(device);
                BluetoothGattService service = findServiceById(gatt, serviceId);

                emit(characteristicDiscoveryStarted(device, service));
                emit(characteristicsDiscovered(device, service,
                        filterCharacteristics(device, service, characteristicIds)));
                bluetoothActionsLoop.actionDone();
            }
        };

        bluetoothActionsLoop.addAction(discoverAction);
    }

    private List<BluetoothGattCharacteristic> filterCharacteristics(BluetoothDevice device,
            BluetoothGattService service, ReadableArray characteristicIds) throws BluetoothException {
        if (characteristicIds == null || characteristicIds.size() == 0) {
            return service.getCharacteristics();
        } else {
            List<BluetoothGattCharacteristic> characteristics = new ArrayList<>();
            for (int index = 0; index < characteristicIds.size(); index++) {
                characteristics.add(findCharacteristic(device, service, characteristicIds.getString(index)));
            }
            return characteristics;
        }
    }

    @ReactMethod
    public void readCharacteristicValue(final ReadableMap characteristicMap) {
        final String deviceId = characteristicMap.getString("deviceId");
        final String serviceId = characteristicMap.getString("serviceId");
        final String characteristicId = characteristicMap.getString("id");

        BluetoothAction readAction = new BluetoothAction(CHARACTERISTIC_READ, deviceId, characteristicId,
                eventEmitter) {
            @Override
            public void run() throws BluetoothException {
                BluetoothGatt gatt = gattCollection.get(deviceId);
                BluetoothGattCharacteristic characteristic = findCharacteristic(gatt, serviceId, characteristicId,
                        BluetoothGattCharacteristic.PROPERTY_READ);

                if (!gatt.readCharacteristic(characteristic)) {
                    throw new BluetoothException("Error when requesting characteristic read");
                }
            }
        };

        bluetoothActionsLoop.addAction(readAction);
    }

    @ReactMethod
    public void writeCharacteristicValue(final ReadableMap characteristicMap, final String base64Value,
            final boolean withResponse) {
        final String deviceId = characteristicMap.getString("deviceId");
        final String serviceId = characteristicMap.getString("serviceId");
        final String characteristicId = characteristicMap.getString("id");

        BluetoothAction writeAction = new BluetoothAction(CHARACTERISTIC_WRITTEN, deviceId, characteristicId,
                eventEmitter) {
            @Override
            public void run() throws BluetoothException {
                BluetoothGatt gatt = gattCollection.get(deviceId);
                BluetoothGattCharacteristic characteristic = findCharacteristic(gatt, serviceId, characteristicId,
                        BluetoothGattCharacteristic.PROPERTY_WRITE);

                characteristic.setValue(Base64.decode(base64Value, Base64.DEFAULT));

                if (withResponse) {
                    characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
                } else {
                    characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
                }

                if (!gatt.writeCharacteristic(characteristic)) {
                    throw new BluetoothException("Error when requesting characteristic write");
                }
            }
        };

        bluetoothActionsLoop.addAction(writeAction);
    }

    @ReactMethod
    public void subscribeToNotification(final ReadableMap characteristicMap) {
        final String deviceId = characteristicMap.getString("deviceId");
        final String serviceId = characteristicMap.getString("serviceId");
        final String characteristicId = characteristicMap.getString("id");

        BluetoothAction subscribeAction = new BluetoothAction(CHARACTERISTIC_NOTIFIED, deviceId, characteristicId,
                eventEmitter) {
            @Override
            public void run() throws BluetoothException {
                BluetoothGatt gatt = gattCollection.get(deviceId);
                BluetoothGattCharacteristic characteristic = findCharacteristic(gatt, serviceId, characteristicId,
                        BluetoothGattCharacteristic.PROPERTY_NOTIFY);

                if (enableNotification(gatt, characteristic)) {
                    Log.d(MODULE_NAME, "Notification enabled");
                } else {
                    throw new BluetoothException("Error when enabling characteristic notification");
                }
            }
        };

        bluetoothActionsLoop.addAction(subscribeAction);
    }

    @ReactMethod
    public void unsubscribeFromNotification(final ReadableMap characteristicMap) {
        final String deviceId = characteristicMap.getString("deviceId");
        final String serviceId = characteristicMap.getString("serviceId");
        final String characteristicId = characteristicMap.getString("id");

        BluetoothAction unsubscribeAction = new BluetoothAction(CHARACTERISTIC_NOTIFIED, deviceId, characteristicId,
                eventEmitter) {
            @Override
            public void run() throws BluetoothException {
                BluetoothGatt gatt = gattCollection.get(deviceId);
                BluetoothGattCharacteristic characteristic = findCharacteristic(gatt, serviceId, characteristicId,
                        BluetoothGattCharacteristic.PROPERTY_NOTIFY);

                if (disableNotification(gatt, characteristic)) {
                    Log.d(MODULE_NAME, "Notification disabled");
                } else {
                    throw new BluetoothException("Error when disabling characteristic notification");
                }
            }
        };

        bluetoothActionsLoop.addAction(unsubscribeAction);
    }

    @Override
    protected void finalize() throws Throwable {
        gattCollection.clear();
        super.finalize();
    }

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();

        constants.put("StateChanged", STATE_CHANGED);
        constants.put("ScanStarted", SCAN_STARTED);
        constants.put("ScanStopped", SCAN_STOPPED);
        constants.put("DeviceDiscovered", DEVICE_DISCOVERED);
        constants.put("DeviceConnected", DEVICE_CONNECTED);
        constants.put("DeviceDisconnected", DEVICE_DISCONNECTED);
        constants.put("ServiceDiscoveryStarted", SERVICE_DISCOVERY_STARTED);
        constants.put("ServiceDiscovered", SERVICES_DISCOVERED);
        constants.put("CharacteristicDiscoveryStarted", CHARACTERISTIC_DISCOVERY_STARTED);
        constants.put("CharacteristicDiscovered", CHARACTERISTICS_DISCOVERED);
        constants.put("CharacteristicRead", CHARACTERISTIC_READ);
        constants.put("CharacteristicWritten", CHARACTERISTIC_WRITTEN);
        constants.put("CharacteristicNotified", CHARACTERISTIC_NOTIFIED);

        return constants;
    }
}