Add All the files from the touchpad_demonstrator Flutter App which can be build for Android and iOS
This commit is contained in:
+74
@@ -0,0 +1,74 @@
|
||||
group 'com.pauldemarco.flutterblue'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.7'
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'com.google.protobuf'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 19
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
dependencies {
|
||||
// Required for local unit tests (JUnit 4 framework)
|
||||
testImplementation 'junit:junit:4.12'
|
||||
implementation 'androidx.core:core:1.0.1'
|
||||
//compile files('/home/paul/flutter/bin/cache/artifacts/engine/android-arm/flutter.jar')
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
proto {
|
||||
srcDir '../protos'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protobuf {
|
||||
// Configure the protoc executable
|
||||
protoc {
|
||||
// Download from repositories
|
||||
artifact = 'com.google.protobuf:protoc:3.6.1'
|
||||
}
|
||||
plugins {
|
||||
javalite {
|
||||
// The codegen for lite comes as a separate artifact
|
||||
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
all().each { task ->
|
||||
task.plugins {
|
||||
javalite { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.protobuf:protobuf-lite:3.0.1'
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = 'flutter_blue'
|
||||
@@ -0,0 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.pauldemarco.flutterblue">
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
</manifest>
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
// Copyright 2017, the Flutter project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package com.pauldemarco.flutterblue;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.pauldemarco.flutterblue.Protos.AdvertisementData;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Parser of Bluetooth Advertisement packets.
|
||||
*/
|
||||
class AdvertisementParser {
|
||||
|
||||
/**
|
||||
* Parses packet data into {@link AdvertisementData} structure.
|
||||
*
|
||||
* @param rawData The scan record data.
|
||||
* @return An AdvertisementData proto object.
|
||||
* @throws ArrayIndexOutOfBoundsException if the input is truncated.
|
||||
*/
|
||||
static AdvertisementData parse(byte[] rawData) {
|
||||
ByteBuffer data = ByteBuffer.wrap(rawData).asReadOnlyBuffer().order(ByteOrder.LITTLE_ENDIAN);
|
||||
AdvertisementData.Builder ret = AdvertisementData.newBuilder();
|
||||
boolean seenLongLocalName = false;
|
||||
do {
|
||||
int length = data.get() & 0xFF;
|
||||
if (length == 0) {
|
||||
break;
|
||||
}
|
||||
if (length > data.remaining()) {
|
||||
throw new ArrayIndexOutOfBoundsException("Not enough data.");
|
||||
}
|
||||
|
||||
int type = data.get() & 0xFF;
|
||||
length--;
|
||||
|
||||
switch (type) {
|
||||
case 0x08: // Short local name.
|
||||
case 0x09: { // Long local name.
|
||||
if (seenLongLocalName) {
|
||||
// Prefer the long name over the short.
|
||||
data.position(data.position() + length);
|
||||
break;
|
||||
}
|
||||
byte[] name = new byte[length];
|
||||
data.get(name);
|
||||
try {
|
||||
ret.setLocalName(new String(name, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (type == 0x09) {
|
||||
seenLongLocalName = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0A: { // Power level.
|
||||
ret.setTxPowerLevel(Protos.Int32Value.newBuilder().setValue(data.get()));
|
||||
break;
|
||||
}
|
||||
case 0x16: // Service Data with 16 bit UUID.
|
||||
case 0x20: // Service Data with 32 bit UUID.
|
||||
case 0x21: { // Service Data with 128 bit UUID.
|
||||
UUID uuid;
|
||||
int remainingDataLength = 0;
|
||||
if (type == 0x16 || type == 0x20) {
|
||||
long uuidValue;
|
||||
if (type == 0x16) {
|
||||
uuidValue = data.getShort() & 0xFFFF;
|
||||
remainingDataLength = length - 2;
|
||||
} else {
|
||||
uuidValue = data.getInt() & 0xFFFFFFFF;
|
||||
remainingDataLength = length - 4;
|
||||
}
|
||||
uuid = UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", uuidValue));
|
||||
} else {
|
||||
long msb = data.getLong();
|
||||
long lsb = data.getLong();
|
||||
uuid = new UUID(msb, lsb);
|
||||
remainingDataLength = length - 16;
|
||||
}
|
||||
byte[] remainingData = new byte[remainingDataLength];
|
||||
data.get(remainingData);
|
||||
ret.putServiceData(uuid.toString(), ByteString.copyFrom(remainingData));
|
||||
break;
|
||||
}
|
||||
case 0xFF: {// Manufacturer specific data.
|
||||
if(length < 2) {
|
||||
throw new ArrayIndexOutOfBoundsException("Not enough data for Manufacturer specific data.");
|
||||
}
|
||||
int manufacturerId = data.getShort();
|
||||
if((length - 2) > 0) {
|
||||
byte[] msd = new byte[length - 2];
|
||||
data.get(msd);
|
||||
ret.putManufacturerData(manufacturerId, ByteString.copyFrom(msd));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
data.position(data.position() + length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
return ret.build();
|
||||
}
|
||||
}
|
||||
+886
@@ -0,0 +1,886 @@
|
||||
// Copyright 2017, Paul DeMarco.
|
||||
// All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package com.pauldemarco.flutterblue;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCallback;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.le.BluetoothLeScanner;
|
||||
import android.bluetooth.le.ScanCallback;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
import android.bluetooth.le.ScanSettings;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelUuid;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
import io.flutter.plugin.common.EventChannel.EventSink;
|
||||
import io.flutter.plugin.common.EventChannel.StreamHandler;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
||||
import io.flutter.plugin.common.MethodChannel.Result;
|
||||
import io.flutter.plugin.common.PluginRegistry.Registrar;
|
||||
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener;
|
||||
|
||||
|
||||
/**
|
||||
* FlutterBluePlugin
|
||||
*/
|
||||
public class FlutterBluePlugin implements MethodCallHandler, RequestPermissionsResultListener {
|
||||
private static final String TAG = "FlutterBluePlugin";
|
||||
private static final String NAMESPACE = "plugins.pauldemarco.com/flutter_blue";
|
||||
private static final int REQUEST_COARSE_LOCATION_PERMISSIONS = 1452;
|
||||
static final private UUID CCCD_ID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
|
||||
private final Registrar registrar;
|
||||
private final MethodChannel channel;
|
||||
private final EventChannel stateChannel;
|
||||
private final EventChannel scanResultChannel;
|
||||
private final EventChannel servicesDiscoveredChannel;
|
||||
private final EventChannel characteristicReadChannel;
|
||||
private final EventChannel descriptorReadChannel;
|
||||
private final BluetoothManager mBluetoothManager;
|
||||
private BluetoothAdapter mBluetoothAdapter;
|
||||
private final Map<String, BluetoothGatt> mGattServers = new HashMap<>();
|
||||
private LogLevel logLevel = LogLevel.EMERGENCY;
|
||||
|
||||
// Pending call and result for startScan, in the case where permissions are needed
|
||||
private MethodCall pendingCall;
|
||||
private Result pendingResult;
|
||||
|
||||
/**
|
||||
* Plugin registration.
|
||||
*/
|
||||
public static void registerWith(Registrar registrar) {
|
||||
final FlutterBluePlugin instance = new FlutterBluePlugin(registrar);
|
||||
registrar.addRequestPermissionsResultListener(instance);
|
||||
}
|
||||
|
||||
FlutterBluePlugin(Registrar r){
|
||||
this.registrar = r;
|
||||
this.channel = new MethodChannel(registrar.messenger(), NAMESPACE+"/methods");
|
||||
this.stateChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/state");
|
||||
this.scanResultChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/scanResult");
|
||||
this.servicesDiscoveredChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/servicesDiscovered");
|
||||
this.characteristicReadChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/characteristicRead");
|
||||
this.descriptorReadChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/descriptorRead");
|
||||
this.mBluetoothManager = (BluetoothManager) r.activity().getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
this.mBluetoothAdapter = mBluetoothManager.getAdapter();
|
||||
channel.setMethodCallHandler(this);
|
||||
stateChannel.setStreamHandler(stateHandler);
|
||||
scanResultChannel.setStreamHandler(scanResultsHandler);
|
||||
servicesDiscoveredChannel.setStreamHandler(servicesDiscoveredHandler);
|
||||
characteristicReadChannel.setStreamHandler(characteristicReadHandler);
|
||||
descriptorReadChannel.setStreamHandler(descriptorReadHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(MethodCall call, Result result) {
|
||||
if(mBluetoothAdapter == null && !"isAvailable".equals(call.method)) {
|
||||
result.error("bluetooth_unavailable", "the device does not have bluetooth", null);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (call.method) {
|
||||
case "setLogLevel":
|
||||
{
|
||||
int logLevelIndex = (int)call.arguments;
|
||||
logLevel = LogLevel.values()[logLevelIndex];
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
|
||||
case "state":
|
||||
{
|
||||
Protos.BluetoothState.Builder p = Protos.BluetoothState.newBuilder();
|
||||
try {
|
||||
switch(mBluetoothAdapter.getState()) {
|
||||
case BluetoothAdapter.STATE_OFF:
|
||||
p.setState(Protos.BluetoothState.State.OFF);
|
||||
break;
|
||||
case BluetoothAdapter.STATE_ON:
|
||||
p.setState(Protos.BluetoothState.State.ON);
|
||||
break;
|
||||
case BluetoothAdapter.STATE_TURNING_OFF:
|
||||
p.setState(Protos.BluetoothState.State.TURNING_OFF);
|
||||
break;
|
||||
case BluetoothAdapter.STATE_TURNING_ON:
|
||||
p.setState(Protos.BluetoothState.State.TURNING_ON);
|
||||
break;
|
||||
default:
|
||||
p.setState(Protos.BluetoothState.State.UNKNOWN);
|
||||
break;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
p.setState(Protos.BluetoothState.State.UNAUTHORIZED);
|
||||
}
|
||||
result.success(p.build().toByteArray());
|
||||
break;
|
||||
}
|
||||
|
||||
case "isAvailable":
|
||||
{
|
||||
result.success(mBluetoothAdapter != null);
|
||||
break;
|
||||
}
|
||||
|
||||
case "isOn":
|
||||
{
|
||||
result.success(mBluetoothAdapter.isEnabled());
|
||||
break;
|
||||
}
|
||||
|
||||
case "startScan":
|
||||
{
|
||||
if (ContextCompat.checkSelfPermission(registrar.activity(), Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(
|
||||
registrar.activity(),
|
||||
new String[] {
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
},
|
||||
REQUEST_COARSE_LOCATION_PERMISSIONS);
|
||||
pendingCall = call;
|
||||
pendingResult = result;
|
||||
break;
|
||||
}
|
||||
startScan(call, result);
|
||||
break;
|
||||
}
|
||||
|
||||
case "stopScan":
|
||||
{
|
||||
stopScan();
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
|
||||
case "connect":
|
||||
{
|
||||
byte[] data = call.arguments();
|
||||
Protos.ConnectRequest options;
|
||||
try {
|
||||
options = Protos.ConnectRequest.newBuilder().mergeFrom(data).build();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
result.error("RuntimeException", e.getMessage(), e);
|
||||
break;
|
||||
}
|
||||
String deviceId = options.getRemoteId();
|
||||
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceId);
|
||||
boolean isConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT).contains(device);
|
||||
|
||||
// If device is already connected, return error
|
||||
if(mGattServers.containsKey(deviceId) && isConnected) {
|
||||
result.error("already_connected", "connection with device already exists", null);
|
||||
return;
|
||||
}
|
||||
|
||||
// If device was connected to previously but is now disconnected, attempt a reconnect
|
||||
if(mGattServers.containsKey(deviceId) && !isConnected) {
|
||||
if(!mGattServers.get(deviceId).connect()){
|
||||
result.error("reconnect_error", "error when reconnecting to device", null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// New request, connect and add gattServer to Map
|
||||
BluetoothGatt gattServer = device.connectGatt(registrar.activity(), options.getAndroidAutoConnect(), mGattCallback);
|
||||
mGattServers.put(deviceId, gattServer);
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
|
||||
case "disconnect":
|
||||
{
|
||||
String deviceId = (String)call.arguments;
|
||||
BluetoothGatt gattServer = mGattServers.remove(deviceId);
|
||||
if(gattServer != null) {
|
||||
gattServer.close();
|
||||
}
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
|
||||
case "deviceState":
|
||||
{
|
||||
String deviceId = (String)call.arguments;
|
||||
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceId);
|
||||
int state = mBluetoothManager.getConnectionState(device, BluetoothProfile.GATT);
|
||||
try {
|
||||
result.success(ProtoMaker.from(device, state).toByteArray());
|
||||
} catch(Exception e) {
|
||||
result.error("device_state_error", e.getMessage(), null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "discoverServices":
|
||||
{
|
||||
String deviceId = (String)call.arguments;
|
||||
BluetoothGatt gattServer = mGattServers.get(deviceId);
|
||||
if(gattServer == null) {
|
||||
result.error("discover_services_error", "no instance of BluetoothGatt, have you connected first?", null);
|
||||
return;
|
||||
}
|
||||
if(gattServer.discoverServices()) {
|
||||
result.success(null);
|
||||
} else {
|
||||
result.error("discover_services_error", "unknown reason", null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "services":
|
||||
{
|
||||
String deviceId = (String)call.arguments;
|
||||
BluetoothGatt gattServer = mGattServers.get(deviceId);
|
||||
if(gattServer == null) {
|
||||
result.error("get_services_error", "no instance of BluetoothGatt, have you connected first?", null);
|
||||
return;
|
||||
}
|
||||
if(gattServer.getServices().isEmpty()) {
|
||||
result.error("get_services_error", "services are empty, have you called discoverServices() yet?", null);
|
||||
return;
|
||||
}
|
||||
Protos.DiscoverServicesResult.Builder p = Protos.DiscoverServicesResult.newBuilder();
|
||||
p.setRemoteId(deviceId);
|
||||
for(BluetoothGattService s : gattServer.getServices()){
|
||||
p.addServices(ProtoMaker.from(gattServer.getDevice(), s, gattServer));
|
||||
}
|
||||
result.success(p.build().toByteArray());
|
||||
break;
|
||||
}
|
||||
|
||||
case "readCharacteristic":
|
||||
{
|
||||
byte[] data = call.arguments();
|
||||
Protos.ReadCharacteristicRequest request;
|
||||
try {
|
||||
request = Protos.ReadCharacteristicRequest.newBuilder().mergeFrom(data).build();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
result.error("RuntimeException", e.getMessage(), e);
|
||||
break;
|
||||
}
|
||||
|
||||
BluetoothGatt gattServer;
|
||||
BluetoothGattCharacteristic characteristic;
|
||||
try {
|
||||
gattServer = locateGatt(request.getRemoteId());
|
||||
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
|
||||
} catch(Exception e) {
|
||||
result.error("read_characteristic_error", e.getMessage(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
if(gattServer.readCharacteristic(characteristic)) {
|
||||
result.success(null);
|
||||
} else {
|
||||
result.error("read_characteristic_error", "unknown reason, may occur if readCharacteristic was called before last read finished.", null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "readDescriptor":
|
||||
{
|
||||
byte[] data = call.arguments();
|
||||
Protos.ReadDescriptorRequest request;
|
||||
try {
|
||||
request = Protos.ReadDescriptorRequest.newBuilder().mergeFrom(data).build();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
result.error("RuntimeException", e.getMessage(), e);
|
||||
break;
|
||||
}
|
||||
|
||||
BluetoothGatt gattServer;
|
||||
BluetoothGattCharacteristic characteristic;
|
||||
BluetoothGattDescriptor descriptor;
|
||||
try {
|
||||
gattServer = locateGatt(request.getRemoteId());
|
||||
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
|
||||
descriptor = locateDescriptor(characteristic, request.getDescriptorUuid());
|
||||
} catch(Exception e) {
|
||||
result.error("read_descriptor_error", e.getMessage(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
if(gattServer.readDescriptor(descriptor)) {
|
||||
result.success(null);
|
||||
} else {
|
||||
result.error("read_descriptor_error", "unknown reason, may occur if readDescriptor was called before last read finished.", null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "writeCharacteristic":
|
||||
{
|
||||
byte[] data = call.arguments();
|
||||
Protos.WriteCharacteristicRequest request;
|
||||
try {
|
||||
request = Protos.WriteCharacteristicRequest.newBuilder().mergeFrom(data).build();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
result.error("RuntimeException", e.getMessage(), e);
|
||||
break;
|
||||
}
|
||||
|
||||
BluetoothGatt gattServer;
|
||||
BluetoothGattCharacteristic characteristic;
|
||||
try {
|
||||
gattServer = locateGatt(request.getRemoteId());
|
||||
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
|
||||
} catch(Exception e) {
|
||||
result.error("write_characteristic_error", e.getMessage(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set characteristic to new value
|
||||
if(!characteristic.setValue(request.getValue().toByteArray())){
|
||||
result.error("write_characteristic_error", "could not set the local value of characteristic", null);
|
||||
}
|
||||
|
||||
// Apply the correct write type
|
||||
if(request.getWriteType() == Protos.WriteCharacteristicRequest.WriteType.WITHOUT_RESPONSE) {
|
||||
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
|
||||
} else {
|
||||
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
|
||||
}
|
||||
|
||||
if(!gattServer.writeCharacteristic(characteristic)){
|
||||
result.error("write_characteristic_error", "writeCharacteristic failed", null);
|
||||
return;
|
||||
}
|
||||
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
|
||||
case "writeDescriptor":
|
||||
{
|
||||
byte[] data = call.arguments();
|
||||
Protos.WriteDescriptorRequest request;
|
||||
try {
|
||||
request = Protos.WriteDescriptorRequest.newBuilder().mergeFrom(data).build();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
result.error("RuntimeException", e.getMessage(), e);
|
||||
break;
|
||||
}
|
||||
|
||||
BluetoothGatt gattServer;
|
||||
BluetoothGattCharacteristic characteristic;
|
||||
BluetoothGattDescriptor descriptor;
|
||||
try {
|
||||
gattServer = locateGatt(request.getRemoteId());
|
||||
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
|
||||
descriptor = locateDescriptor(characteristic, request.getDescriptorUuid());
|
||||
} catch(Exception e) {
|
||||
result.error("write_descriptor_error", e.getMessage(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set descriptor to new value
|
||||
if(!descriptor.setValue(request.getValue().toByteArray())){
|
||||
result.error("write_descriptor_error", "could not set the local value for descriptor", null);
|
||||
}
|
||||
|
||||
if(!gattServer.writeDescriptor(descriptor)){
|
||||
result.error("write_descriptor_error", "writeCharacteristic failed", null);
|
||||
return;
|
||||
}
|
||||
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
|
||||
case "setNotification":
|
||||
{
|
||||
byte[] data = call.arguments();
|
||||
Protos.SetNotificationRequest request;
|
||||
try {
|
||||
request = Protos.SetNotificationRequest.newBuilder().mergeFrom(data).build();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
result.error("RuntimeException", e.getMessage(), e);
|
||||
break;
|
||||
}
|
||||
|
||||
BluetoothGatt gattServer;
|
||||
BluetoothGattCharacteristic characteristic;
|
||||
BluetoothGattDescriptor cccDescriptor;
|
||||
try {
|
||||
gattServer = locateGatt(request.getRemoteId());
|
||||
characteristic = locateCharacteristic(gattServer, request.getServiceUuid(), request.getSecondaryServiceUuid(), request.getCharacteristicUuid());
|
||||
cccDescriptor = characteristic.getDescriptor(CCCD_ID);
|
||||
if(cccDescriptor == null) {
|
||||
throw new Exception("could not locate CCCD descriptor for characteristic: " +characteristic.getUuid().toString());
|
||||
}
|
||||
} catch(Exception e) {
|
||||
result.error("set_notification_error", e.getMessage(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] value = null;
|
||||
|
||||
if(request.getEnable()) {
|
||||
boolean canNotify = (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0;
|
||||
boolean canIndicate = (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0;
|
||||
if(!canIndicate && !canNotify) {
|
||||
result.error("set_notification_error", "the characteristic cannot notify or indicate", null);
|
||||
return;
|
||||
}
|
||||
if(canIndicate) {
|
||||
value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
|
||||
}
|
||||
if(canNotify) {
|
||||
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
|
||||
}
|
||||
} else {
|
||||
value = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
|
||||
}
|
||||
|
||||
if(!gattServer.setCharacteristicNotification(characteristic, request.getEnable())){
|
||||
result.error("set_notification_error", "could not set characteristic notifications to :" + request.getEnable(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!cccDescriptor.setValue(value)) {
|
||||
result.error("set_notification_error", "error when setting the descriptor value to: " + value, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!gattServer.writeDescriptor(cccDescriptor)) {
|
||||
result.error("set_notification_error", "error when writing the descriptor", null);
|
||||
return;
|
||||
}
|
||||
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
result.notImplemented();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onRequestPermissionsResult(
|
||||
int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (requestCode == REQUEST_COARSE_LOCATION_PERMISSIONS) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startScan(pendingCall, pendingResult);
|
||||
} else {
|
||||
pendingResult.error(
|
||||
"no_permissions", "flutter_blue plugin requires location permissions for scanning", null);
|
||||
pendingResult = null;
|
||||
pendingCall = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private BluetoothGatt locateGatt(String remoteId) throws Exception {
|
||||
BluetoothGatt gattServer = mGattServers.get(remoteId);
|
||||
if(gattServer == null) {
|
||||
throw new Exception("no instance of BluetoothGatt, have you connected first?");
|
||||
}
|
||||
return gattServer;
|
||||
}
|
||||
|
||||
private BluetoothGattCharacteristic locateCharacteristic(BluetoothGatt gattServer, String serviceId, String secondaryServiceId, String characteristicId) throws Exception {
|
||||
BluetoothGattService primaryService = gattServer.getService(UUID.fromString(serviceId));
|
||||
if(primaryService == null) {
|
||||
throw new Exception("service (" + serviceId + ") could not be located on the device");
|
||||
}
|
||||
BluetoothGattService secondaryService = null;
|
||||
if(secondaryServiceId.length() > 0) {
|
||||
for(BluetoothGattService s : primaryService.getIncludedServices()){
|
||||
if(s.getUuid().equals(UUID.fromString(secondaryServiceId))){
|
||||
secondaryService = s;
|
||||
}
|
||||
}
|
||||
if(secondaryService == null) {
|
||||
throw new Exception("secondary service (" + secondaryServiceId + ") could not be located on the device");
|
||||
}
|
||||
}
|
||||
BluetoothGattService service = (secondaryService != null) ? secondaryService : primaryService;
|
||||
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(characteristicId));
|
||||
if(characteristic == null) {
|
||||
throw new Exception("characteristic (" + characteristicId + ") could not be located in the service ("+service.getUuid().toString()+")");
|
||||
}
|
||||
return characteristic;
|
||||
}
|
||||
|
||||
private BluetoothGattDescriptor locateDescriptor(BluetoothGattCharacteristic characteristic, String descriptorId) throws Exception {
|
||||
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(descriptorId));
|
||||
if(descriptor == null) {
|
||||
throw new Exception("descriptor (" + descriptorId + ") could not be located in the characteristic ("+characteristic.getUuid().toString()+")");
|
||||
}
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
private final StreamHandler stateHandler = new StreamHandler() {
|
||||
private EventSink sink;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
|
||||
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
|
||||
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
|
||||
BluetoothAdapter.ERROR);
|
||||
switch (state) {
|
||||
case BluetoothAdapter.STATE_OFF:
|
||||
sink.success(Protos.BluetoothState.newBuilder().setState(Protos.BluetoothState.State.OFF).build().toByteArray());
|
||||
break;
|
||||
case BluetoothAdapter.STATE_TURNING_OFF:
|
||||
sink.success(Protos.BluetoothState.newBuilder().setState(Protos.BluetoothState.State.TURNING_OFF).build().toByteArray());
|
||||
break;
|
||||
case BluetoothAdapter.STATE_ON:
|
||||
sink.success(Protos.BluetoothState.newBuilder().setState(Protos.BluetoothState.State.ON).build().toByteArray());
|
||||
break;
|
||||
case BluetoothAdapter.STATE_TURNING_ON:
|
||||
sink.success(Protos.BluetoothState.newBuilder().setState(Protos.BluetoothState.State.TURNING_ON).build().toByteArray());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onListen(Object o, EventChannel.EventSink eventSink) {
|
||||
sink = eventSink;
|
||||
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
|
||||
registrar.activity().registerReceiver(mReceiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object o) {
|
||||
sink = null;
|
||||
registrar.activity().unregisterReceiver(mReceiver);
|
||||
}
|
||||
};
|
||||
|
||||
private void startScan(MethodCall call, Result result) {
|
||||
byte[] data = call.arguments();
|
||||
Protos.ScanSettings settings;
|
||||
try {
|
||||
settings = Protos.ScanSettings.newBuilder().mergeFrom(data).build();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
startScan21(settings);
|
||||
} else {
|
||||
startScan18(settings);
|
||||
}
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
result.error("startScan", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopScan() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
stopScan21();
|
||||
} else {
|
||||
stopScan18();
|
||||
}
|
||||
}
|
||||
|
||||
private ScanCallback scanCallback21;
|
||||
|
||||
@TargetApi(21)
|
||||
private ScanCallback getScanCallback21() {
|
||||
if(scanCallback21 == null){
|
||||
scanCallback21 = new ScanCallback() {
|
||||
|
||||
@Override
|
||||
public void onScanResult(int callbackType, ScanResult result) {
|
||||
super.onScanResult(callbackType, result);
|
||||
if(scanResultsSink != null) {
|
||||
Protos.ScanResult scanResult = ProtoMaker.from(result.getDevice(), result);
|
||||
scanResultsSink.success(scanResult.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchScanResults(List<ScanResult> results) {
|
||||
super.onBatchScanResults(results);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScanFailed(int errorCode) {
|
||||
super.onScanFailed(errorCode);
|
||||
}
|
||||
};
|
||||
}
|
||||
return scanCallback21;
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
private void startScan21(Protos.ScanSettings proto) throws IllegalStateException {
|
||||
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
|
||||
if(scanner == null) throw new IllegalStateException("getBluetoothLeScanner() is null. Is the Adapter on?");
|
||||
int scanMode = proto.getAndroidScanMode();
|
||||
int count = proto.getServiceUuidsCount();
|
||||
List<ScanFilter> filters = new ArrayList<>(count);
|
||||
for(int i = 0; i < count; i++) {
|
||||
String uuid = proto.getServiceUuids(i);
|
||||
ScanFilter f = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuid)).build();
|
||||
filters.add(f);
|
||||
}
|
||||
ScanSettings settings = new ScanSettings.Builder().setScanMode(scanMode).build();
|
||||
scanner.startScan(filters, settings, getScanCallback21());
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
private void stopScan21() {
|
||||
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
|
||||
if(scanner != null) scanner.stopScan(getScanCallback21());
|
||||
}
|
||||
|
||||
private BluetoothAdapter.LeScanCallback scanCallback18;
|
||||
|
||||
private BluetoothAdapter.LeScanCallback getScanCallback18() {
|
||||
if(scanCallback18 == null) {
|
||||
scanCallback18 = new BluetoothAdapter.LeScanCallback() {
|
||||
@Override
|
||||
public void onLeScan(final BluetoothDevice bluetoothDevice, int rssi,
|
||||
byte[] scanRecord) {
|
||||
if(scanResultsSink != null) {
|
||||
Protos.ScanResult scanResult = ProtoMaker.from(bluetoothDevice, scanRecord, rssi);
|
||||
scanResultsSink.success(scanResult.toByteArray());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return scanCallback18;
|
||||
}
|
||||
|
||||
private void startScan18(Protos.ScanSettings proto) throws IllegalStateException {
|
||||
List<String> serviceUuids = proto.getServiceUuidsList();
|
||||
UUID[] uuids = new UUID[serviceUuids.size()];
|
||||
for(int i = 0; i < serviceUuids.size(); i++) {
|
||||
uuids[i] = UUID.fromString(serviceUuids.get(i));
|
||||
}
|
||||
boolean success = mBluetoothAdapter.startLeScan(uuids, getScanCallback18());
|
||||
if(!success) throw new IllegalStateException("getBluetoothLeScanner() is null. Is the Adapter on?");
|
||||
}
|
||||
|
||||
private void stopScan18() {
|
||||
mBluetoothAdapter.stopLeScan(getScanCallback18());
|
||||
}
|
||||
|
||||
private EventSink scanResultsSink;
|
||||
private final StreamHandler scanResultsHandler = new StreamHandler() {
|
||||
@Override
|
||||
public void onListen(Object o, EventChannel.EventSink eventSink) {
|
||||
scanResultsSink = eventSink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object o) {
|
||||
scanResultsSink = null;
|
||||
}
|
||||
};
|
||||
|
||||
private EventSink servicesDiscoveredSink;
|
||||
private final StreamHandler servicesDiscoveredHandler = new StreamHandler() {
|
||||
@Override
|
||||
public void onListen(Object o, EventChannel.EventSink eventSink) {
|
||||
servicesDiscoveredSink = eventSink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object o) {
|
||||
servicesDiscoveredSink = null;
|
||||
}
|
||||
};
|
||||
|
||||
private EventSink characteristicReadSink;
|
||||
private final StreamHandler characteristicReadHandler = new StreamHandler() {
|
||||
@Override
|
||||
public void onListen(Object o, EventChannel.EventSink eventSink) {
|
||||
characteristicReadSink = eventSink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object o) {
|
||||
characteristicReadSink = null;
|
||||
}
|
||||
};
|
||||
|
||||
private EventSink descriptorReadSink;
|
||||
private final StreamHandler descriptorReadHandler = new StreamHandler() {
|
||||
@Override
|
||||
public void onListen(Object o, EventChannel.EventSink eventSink) {
|
||||
descriptorReadSink = eventSink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object o) {
|
||||
descriptorReadSink = null;
|
||||
}
|
||||
};
|
||||
|
||||
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
|
||||
@Override
|
||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||
log(LogLevel.DEBUG, "[onConnectionStateChange] status: " + status + " newState: " + newState);
|
||||
channel.invokeMethod("DeviceState", ProtoMaker.from(gatt.getDevice(), newState).toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
||||
log(LogLevel.DEBUG, "[onServicesDiscovered] count: " + gatt.getServices().size() + " status: " + status);
|
||||
if(servicesDiscoveredSink != null) {
|
||||
Protos.DiscoverServicesResult.Builder p = Protos.DiscoverServicesResult.newBuilder();
|
||||
p.setRemoteId(gatt.getDevice().getAddress());
|
||||
for(BluetoothGattService s : gatt.getServices()) {
|
||||
p.addServices(ProtoMaker.from(gatt.getDevice(), s, gatt));
|
||||
}
|
||||
servicesDiscoveredSink.success(p.build().toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
log(LogLevel.DEBUG, "[onCharacteristicRead] uuid: " + characteristic.getUuid().toString() + " status: " + status);
|
||||
if(characteristicReadSink != null) {
|
||||
Protos.ReadCharacteristicResponse.Builder p = Protos.ReadCharacteristicResponse.newBuilder();
|
||||
p.setRemoteId(gatt.getDevice().getAddress());
|
||||
p.setCharacteristic(ProtoMaker.from(characteristic, gatt));
|
||||
characteristicReadSink.success(p.build().toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
log(LogLevel.DEBUG, "[onCharacteristicWrite] uuid: " + characteristic.getUuid().toString() + " status: " + status);
|
||||
Protos.WriteCharacteristicRequest.Builder request = Protos.WriteCharacteristicRequest.newBuilder();
|
||||
request.setRemoteId(gatt.getDevice().getAddress());
|
||||
request.setCharacteristicUuid(characteristic.getUuid().toString());
|
||||
request.setServiceUuid(characteristic.getService().getUuid().toString());
|
||||
Protos.WriteCharacteristicResponse.Builder p = Protos.WriteCharacteristicResponse.newBuilder();
|
||||
p.setRequest(request);
|
||||
p.setSuccess(status == BluetoothGatt.GATT_SUCCESS);
|
||||
channel.invokeMethod("WriteCharacteristicResponse", p.build().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||
log(LogLevel.DEBUG, "[onCharacteristicChanged] uuid: " + characteristic.getUuid().toString());
|
||||
Protos.OnNotificationResponse.Builder p = Protos.OnNotificationResponse.newBuilder();
|
||||
p.setRemoteId(gatt.getDevice().getAddress());
|
||||
p.setCharacteristic(ProtoMaker.from(characteristic, gatt));
|
||||
channel.invokeMethod("OnValueChanged", p.build().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
log(LogLevel.DEBUG, "[onDescriptorRead] uuid: " + descriptor.getUuid().toString() + " status: " + status);
|
||||
if(descriptorReadSink != null) {
|
||||
// Rebuild the ReadAttributeRequest and send back along with response
|
||||
Protos.ReadDescriptorRequest.Builder q = Protos.ReadDescriptorRequest.newBuilder();
|
||||
q.setRemoteId(gatt.getDevice().getAddress());
|
||||
q.setCharacteristicUuid(descriptor.getCharacteristic().getUuid().toString());
|
||||
q.setDescriptorUuid(descriptor.getUuid().toString());
|
||||
if(descriptor.getCharacteristic().getService().getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY) {
|
||||
q.setServiceUuid(descriptor.getCharacteristic().getService().getUuid().toString());
|
||||
} else {
|
||||
// Reverse search to find service
|
||||
for(BluetoothGattService s : gatt.getServices()) {
|
||||
for(BluetoothGattService ss : s.getIncludedServices()) {
|
||||
if(ss.getUuid().equals(descriptor.getCharacteristic().getService().getUuid())){
|
||||
q.setServiceUuid(s.getUuid().toString());
|
||||
q.setSecondaryServiceUuid(ss.getUuid().toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Protos.ReadDescriptorResponse.Builder p = Protos.ReadDescriptorResponse.newBuilder();
|
||||
p.setRequest(q);
|
||||
p.setValue(ByteString.copyFrom(descriptor.getValue()));
|
||||
descriptorReadSink.success(p.build().toByteArray());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
||||
log(LogLevel.DEBUG, "[onDescriptorWrite] uuid: " + descriptor.getUuid().toString() + " status: " + status);
|
||||
Protos.WriteDescriptorRequest.Builder request = Protos.WriteDescriptorRequest.newBuilder();
|
||||
request.setRemoteId(gatt.getDevice().getAddress());
|
||||
request.setDescriptorUuid(descriptor.getUuid().toString());
|
||||
request.setCharacteristicUuid(descriptor.getCharacteristic().getUuid().toString());
|
||||
request.setServiceUuid(descriptor.getCharacteristic().getService().getUuid().toString());
|
||||
Protos.WriteDescriptorResponse.Builder p = Protos.WriteDescriptorResponse.newBuilder();
|
||||
p.setRequest(request);
|
||||
p.setSuccess(status == BluetoothGatt.GATT_SUCCESS);
|
||||
channel.invokeMethod("WriteDescriptorResponse", p.build().toByteArray());
|
||||
|
||||
if(descriptor.getUuid().compareTo(CCCD_ID) == 0) {
|
||||
// SetNotificationResponse
|
||||
Protos.SetNotificationResponse.Builder q = Protos.SetNotificationResponse.newBuilder();
|
||||
q.setRemoteId(gatt.getDevice().getAddress());
|
||||
q.setCharacteristic(ProtoMaker.from(descriptor.getCharacteristic(), gatt));
|
||||
channel.invokeMethod("SetNotificationResponse", q.build().toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
|
||||
log(LogLevel.DEBUG, "[onReliableWriteCompleted] status: " + status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
|
||||
log(LogLevel.DEBUG, "[onReadRemoteRssi] rssi: " + rssi + " status: " + status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
||||
log(LogLevel.DEBUG, "[onMtuChanged] mtu: " + mtu + " status: " + status);
|
||||
}
|
||||
};
|
||||
|
||||
enum LogLevel
|
||||
{
|
||||
EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG;
|
||||
}
|
||||
|
||||
private void log(LogLevel level, String message) {
|
||||
if(level.ordinal() <= logLevel.ordinal()) {
|
||||
Log.d(TAG, message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
ios/.symlinks/plugins/flutter_blue/android/src/main/java/com/pauldemarco/flutterblue/ProtoMaker.java
Executable
+206
@@ -0,0 +1,206 @@
|
||||
// Copyright 2017, Paul DeMarco.
|
||||
// All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
package com.pauldemarco.flutterblue;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.bluetooth.le.ScanRecord;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelUuid;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by paul on 8/31/17.
|
||||
*/
|
||||
|
||||
public class ProtoMaker {
|
||||
|
||||
private static final UUID CCCD_UUID = UUID.fromString("000002902-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
static Protos.ScanResult from(BluetoothDevice device, byte[] advertisementData, int rssi) {
|
||||
Protos.ScanResult.Builder p = Protos.ScanResult.newBuilder();
|
||||
p.setDevice(from(device));
|
||||
if(advertisementData != null && advertisementData.length > 0)
|
||||
p.setAdvertisementData(AdvertisementParser.parse(advertisementData));
|
||||
p.setRssi(rssi);
|
||||
return p.build();
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
static Protos.ScanResult from(BluetoothDevice device, ScanResult scanResult) {
|
||||
Protos.ScanResult.Builder p = Protos.ScanResult.newBuilder();
|
||||
p.setDevice(from(device));
|
||||
Protos.AdvertisementData.Builder a = Protos.AdvertisementData.newBuilder();
|
||||
ScanRecord scanRecord = scanResult.getScanRecord();
|
||||
if(Build.VERSION.SDK_INT >= 26) {
|
||||
a.setConnectable(scanResult.isConnectable());
|
||||
} else {
|
||||
if(scanRecord != null) {
|
||||
int flags = scanRecord.getAdvertiseFlags();
|
||||
a.setConnectable((flags & 0x2) > 0);
|
||||
}
|
||||
}
|
||||
if(scanRecord != null) {
|
||||
String deviceName = scanRecord.getDeviceName();
|
||||
if(deviceName != null) {
|
||||
a.setLocalName(deviceName);
|
||||
}
|
||||
int txPower = scanRecord.getTxPowerLevel();
|
||||
if(txPower != Integer.MIN_VALUE) {
|
||||
a.setTxPowerLevel(Protos.Int32Value.newBuilder().setValue(txPower));
|
||||
}
|
||||
// Manufacturer Specific Data
|
||||
SparseArray<byte[]> msd = scanRecord.getManufacturerSpecificData();
|
||||
for (int i = 0; i < msd.size(); i++) {
|
||||
int key = msd.keyAt(i);
|
||||
byte[] value = msd.valueAt(i);
|
||||
a.putManufacturerData(key, ByteString.copyFrom(value));
|
||||
}
|
||||
// Service Data
|
||||
Map<ParcelUuid, byte[]> serviceData = scanRecord.getServiceData();
|
||||
for (Map.Entry<ParcelUuid, byte[]> entry : serviceData.entrySet()) {
|
||||
ParcelUuid key = entry.getKey();
|
||||
byte[] value = entry.getValue();
|
||||
a.putServiceData(key.getUuid().toString(), ByteString.copyFrom(value));
|
||||
}
|
||||
// Service UUIDs
|
||||
List<ParcelUuid> serviceUuids = scanRecord.getServiceUuids();
|
||||
if(serviceUuids != null) {
|
||||
for (ParcelUuid s : serviceUuids) {
|
||||
a.addServiceUuids(s.getUuid().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
p.setRssi(scanResult.getRssi());
|
||||
p.setAdvertisementData(a.build());
|
||||
return p.build();
|
||||
}
|
||||
|
||||
static Protos.BluetoothDevice from(BluetoothDevice device) {
|
||||
Protos.BluetoothDevice.Builder p = Protos.BluetoothDevice.newBuilder();
|
||||
p.setRemoteId(device.getAddress());
|
||||
String name = device.getName();
|
||||
if(name != null) {
|
||||
p.setName(name);
|
||||
}
|
||||
switch(device.getType()){
|
||||
case BluetoothDevice.DEVICE_TYPE_LE:
|
||||
p.setType(Protos.BluetoothDevice.Type.LE);
|
||||
break;
|
||||
case BluetoothDevice.DEVICE_TYPE_CLASSIC:
|
||||
p.setType(Protos.BluetoothDevice.Type.CLASSIC);
|
||||
break;
|
||||
case BluetoothDevice.DEVICE_TYPE_DUAL:
|
||||
p.setType(Protos.BluetoothDevice.Type.DUAL);
|
||||
break;
|
||||
default:
|
||||
p.setType(Protos.BluetoothDevice.Type.UNKNOWN);
|
||||
break;
|
||||
}
|
||||
return p.build();
|
||||
}
|
||||
|
||||
static Protos.BluetoothService from(BluetoothDevice device, BluetoothGattService service, BluetoothGatt gatt) {
|
||||
Protos.BluetoothService.Builder p = Protos.BluetoothService.newBuilder();
|
||||
p.setRemoteId(device.getAddress());
|
||||
p.setUuid(service.getUuid().toString());
|
||||
p.setIsPrimary(service.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY);
|
||||
for(BluetoothGattCharacteristic c : service.getCharacteristics()) {
|
||||
p.addCharacteristics(from(c, gatt));
|
||||
}
|
||||
for(BluetoothGattService s : service.getIncludedServices()) {
|
||||
p.addIncludedServices(from(device, s, gatt));
|
||||
}
|
||||
return p.build();
|
||||
}
|
||||
|
||||
static Protos.BluetoothCharacteristic from(BluetoothGattCharacteristic characteristic, BluetoothGatt gatt) {
|
||||
Protos.BluetoothCharacteristic.Builder p = Protos.BluetoothCharacteristic.newBuilder();
|
||||
p.setUuid(characteristic.getUuid().toString());
|
||||
p.setProperties(from(characteristic.getProperties()));
|
||||
if(characteristic.getValue() != null)
|
||||
p.setValue(ByteString.copyFrom(characteristic.getValue()));
|
||||
for(BluetoothGattDescriptor d : characteristic.getDescriptors()) {
|
||||
p.addDescriptors(from(d));
|
||||
}
|
||||
if(characteristic.getService().getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY) {
|
||||
p.setServiceUuid(characteristic.getService().getUuid().toString());
|
||||
} else {
|
||||
// Reverse search to find service
|
||||
for(BluetoothGattService s : gatt.getServices()) {
|
||||
for(BluetoothGattService ss : s.getIncludedServices()) {
|
||||
if(ss.getUuid().equals(characteristic.getService().getUuid())){
|
||||
p.setServiceUuid(s.getUuid().toString());
|
||||
p.setSecondaryServiceUuid(ss.getUuid().toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return p.build();
|
||||
}
|
||||
|
||||
static Protos.BluetoothDescriptor from(BluetoothGattDescriptor descriptor) {
|
||||
Protos.BluetoothDescriptor.Builder p = Protos.BluetoothDescriptor.newBuilder();
|
||||
p.setUuid(descriptor.getUuid().toString());
|
||||
p.setCharacteristicUuid(descriptor.getCharacteristic().getUuid().toString());
|
||||
p.setServiceUuid(descriptor.getCharacteristic().getService().getUuid().toString());
|
||||
if(descriptor.getValue() != null)
|
||||
p.setValue(ByteString.copyFrom(descriptor.getValue()));
|
||||
return p.build();
|
||||
}
|
||||
|
||||
static Protos.CharacteristicProperties from(int properties) {
|
||||
return Protos.CharacteristicProperties.newBuilder()
|
||||
.setBroadcast((properties & 1) != 0)
|
||||
.setRead((properties & 2) != 0)
|
||||
.setWriteWithoutResponse((properties & 4) != 0)
|
||||
.setWrite((properties & 8) != 0)
|
||||
.setNotify((properties & 16) != 0)
|
||||
.setIndicate((properties & 32) != 0)
|
||||
.setAuthenticatedSignedWrites((properties & 64) != 0)
|
||||
.setExtendedProperties((properties & 128) != 0)
|
||||
.setNotifyEncryptionRequired((properties & 256) != 0)
|
||||
.setIndicateEncryptionRequired((properties & 512) != 0)
|
||||
.build();
|
||||
}
|
||||
|
||||
static Protos.DeviceStateResponse from(BluetoothDevice device, int state) {
|
||||
Protos.DeviceStateResponse.Builder p = Protos.DeviceStateResponse.newBuilder();
|
||||
switch(state) {
|
||||
case BluetoothProfile.STATE_DISCONNECTING:
|
||||
p.setState(Protos.DeviceStateResponse.BluetoothDeviceState.DISCONNECTING);
|
||||
break;
|
||||
case BluetoothProfile.STATE_CONNECTED:
|
||||
p.setState(Protos.DeviceStateResponse.BluetoothDeviceState.CONNECTED);
|
||||
break;
|
||||
case BluetoothProfile.STATE_CONNECTING:
|
||||
p.setState(Protos.DeviceStateResponse.BluetoothDeviceState.CONNECTING);
|
||||
break;
|
||||
case BluetoothProfile.STATE_DISCONNECTED:
|
||||
p.setState(Protos.DeviceStateResponse.BluetoothDeviceState.DISCONNECTED);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
p.setRemoteId(device.getAddress());
|
||||
return p.build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user