diff --git a/.metadata b/.metadata
index 9796a7e5..088e2dba 100644
--- a/.metadata
+++ b/.metadata
@@ -1,10 +1,10 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: f53b32eb2317ba09137969999d130c24a6314997
- channel: master
-
-project_type: plugin
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: f53b32eb2317ba09137969999d130c24a6314997
+ channel: master
+
+project_type: plugin
diff --git a/android/build.gradle b/android/build.gradle
index 6a2ce0de..ba1c40e8 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,58 +1,74 @@
-group 'com.pauldemarco.flutter_blue'
-version '1.0'
-
-buildscript {
- repositories {
- google()
- jcenter()
- }
-
- dependencies {
- classpath 'com.android.tools.build:gradle:4.1.0'
- classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.15'
- }
-}
-
-rootProject.allprojects {
- repositories {
- google()
- jcenter()
- }
-}
-
-apply plugin: 'com.android.library'
-apply plugin: 'com.google.protobuf'
-
-android {
- compileSdkVersion 30
-
- defaultConfig {
- minSdkVersion 19
- }
- sourceSets {
- main {
- proto {
- srcDir '../protos'
- }
- }
- }
-}
-
-protobuf {
- protoc {
- artifact = 'com.google.protobuf:protoc:3.11.4'
- }
- generateProtoTasks {
- all().each { task ->
- task.builtins {
- java {
- option "lite"
- }
- }
- }
- }
-}
-
-dependencies {
- implementation 'com.google.protobuf:protobuf-javalite:3.11.4'
+group 'com.pauldemarco.flutter_blue'
+version '1.0'
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:4.1.0'
+ classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.15'
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'com.google.protobuf'
+
+android {
+ compileSdkVersion 31
+
+ defaultConfig {
+ minSdkVersion 19
+ targetSdkVersion 31
+ // Use Java 8 language features and APIs
+ // https://developer.android.com/studio/write/java8-support
+ multiDexEnabled true
+ }
+
+ compileOptions {
+ // Use Java 8 language features and APIs
+ // https://developer.android.com/studio/write/java8-support
+ // Flag to enable support for the new language APIs
+ coreLibraryDesugaringEnabled true
+ // Sets Java compatibility to Java 8
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ sourceSets {
+ main {
+ proto {
+ srcDir '../protos'
+ }
+ }
+ }
+}
+
+protobuf {
+ protoc {
+ artifact = 'com.google.protobuf:protoc:3.11.4'
+ }
+ generateProtoTasks {
+ all().each { task ->
+ task.builtins {
+ java {
+ option "lite"
+ }
+ }
+ }
+ }
+}
+
+dependencies {
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
+ implementation 'com.google.protobuf:protobuf-javalite:3.11.4'
}
\ No newline at end of file
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index a4c5571c..7d047531 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,6 +1,15 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/src/main/java/com/pauldemarco/flutter_blue/FlutterBluePlugin.java b/android/src/main/java/com/pauldemarco/flutter_blue/FlutterBluePlugin.java
index f3ffbdb9..3da6ec10 100644
--- a/android/src/main/java/com/pauldemarco/flutter_blue/FlutterBluePlugin.java
+++ b/android/src/main/java/com/pauldemarco/flutter_blue/FlutterBluePlugin.java
@@ -77,14 +77,17 @@ public class FlutterBluePlugin implements FlutterPlugin, ActivityAware, MethodCa
private Application application;
private Activity activity;
- private static final int REQUEST_FINE_LOCATION_PERMISSIONS = 1452;
static final private UUID CCCD_ID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private final Map mDevices = new HashMap<>();
private LogLevel logLevel = LogLevel.EMERGENCY;
+
+ private interface OperationOnPermission {
+ public void op(boolean granted, String permission);
+ }
+
+ private int lastEventId = 1452;
+ private Map operationsOnPermission = new HashMap();
- // Pending call and result for startScan, in the case where permissions are needed
- private MethodCall pendingCall;
- private Result pendingResult;
private ArrayList macDeviceScanned = new ArrayList<>();
private boolean allowDuplicates = false;
@@ -238,19 +241,13 @@ public void onMethodCall(MethodCall call, Result result) {
case "startScan":
{
- if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
- != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(
- activityBinding.getActivity(),
- new String[] {
- Manifest.permission.ACCESS_FINE_LOCATION
- },
- REQUEST_FINE_LOCATION_PERMISSIONS);
- pendingCall = call;
- pendingResult = result;
- break;
- }
- startScan(call, result);
+ ensurePermissionBeforeAction(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_FINE_LOCATION, (granted, permission) -> {
+ if (granted)
+ startScan(call, result);
+ else
+ result.error(
+ "no_permissions", String.format("flutter_blue plugin requires %s for scanning", permission), null);
+ });
break;
}
@@ -263,55 +260,69 @@ public void onMethodCall(MethodCall call, Result result) {
case "getConnectedDevices":
{
- List devices = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
- Protos.ConnectedDevicesResponse.Builder p = Protos.ConnectedDevicesResponse.newBuilder();
- for(BluetoothDevice d : devices) {
- p.addDevices(ProtoMaker.from(d));
- }
- result.success(p.build().toByteArray());
- log(LogLevel.EMERGENCY, "mDevices size: " + mDevices.size());
+ ensurePermissionBeforeAction(Manifest.permission.BLUETOOTH_CONNECT, null, (granted, permission) -> {
+ if (!granted) {
+ result.error(
+ "no_permissions", String.format("flutter_blue plugin requires %s for obtaining connected devices", permission), null);
+ return;
+ }
+ List devices = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
+ Protos.ConnectedDevicesResponse.Builder p = Protos.ConnectedDevicesResponse.newBuilder();
+ for (BluetoothDevice d : devices) {
+ p.addDevices(ProtoMaker.from(d));
+ }
+ result.success(p.build().toByteArray());
+ log(LogLevel.EMERGENCY, "mDevices size: " + mDevices.size());
+ });
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);
+ ensurePermissionBeforeAction(Manifest.permission.BLUETOOTH_CONNECT, null, (granted, permission) -> {
+ if (!granted) {
+ result.error(
+ "no_permissions", String.format("flutter_blue plugin requires %s for new connection", permission), null);
+ return;
+ }
+ byte[] data = call.arguments();
+ Protos.ConnectRequest options;
+ try {
+ options = Protos.ConnectRequest.newBuilder().mergeFrom(data).build();
+ } catch (InvalidProtocolBufferException e) {
+ result.error("RuntimeException", e.getMessage(), e);
+ return;
+ }
+ 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(mDevices.containsKey(deviceId) && isConnected) {
- result.error("already_connected", "connection with device already exists", null);
- return;
- }
+ // If device is already connected, return error
+ if(mDevices.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(mDevices.containsKey(deviceId) && !isConnected) {
- if(mDevices.get(deviceId).gatt.connect()){
- result.success(null);
- } else {
- result.error("reconnect_error", "error when reconnecting to device", null);
+ // If device was connected to previously but is now disconnected, attempt a reconnect
+ if(mDevices.containsKey(deviceId) && !isConnected) {
+ if(mDevices.get(deviceId).gatt.connect()){
+ result.success(null);
+ } else {
+ result.error("reconnect_error", "error when reconnecting to device", null);
+ }
+ return;
}
- return;
- }
- // New request, connect and add gattServer to Map
- BluetoothGatt gattServer;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- gattServer = device.connectGatt(context, options.getAndroidAutoConnect(), mGattCallback, BluetoothDevice.TRANSPORT_LE);
- } else {
- gattServer = device.connectGatt(context, options.getAndroidAutoConnect(), mGattCallback);
- }
- mDevices.put(deviceId, new BluetoothDeviceCache(gattServer));
- result.success(null);
+ // New request, connect and add gattServer to Map
+ BluetoothGatt gattServer;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ gattServer = device.connectGatt(context, options.getAndroidAutoConnect(), mGattCallback, BluetoothDevice.TRANSPORT_LE);
+ } else {
+ gattServer = device.connectGatt(context, options.getAndroidAutoConnect(), mGattCallback);
+ }
+ mDevices.put(deviceId, new BluetoothDeviceCache(gattServer));
+ result.success(null);
+ });
break;
}
@@ -635,18 +646,31 @@ public void onMethodCall(MethodCall call, Result result) {
}
}
+ void ensurePermissionBeforeAction(String permissionA12, String permission, OperationOnPermission operation) {
+ permission = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? permissionA12 : permission;
+ if (permission != null && ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
+ operationsOnPermission.put(lastEventId, (granted, perm) -> {
+ operationsOnPermission.remove(lastEventId);
+ operation.op(granted, perm);
+ });
+ ActivityCompat.requestPermissions(
+ activityBinding.getActivity(),
+ new String[] {
+ permission
+ },
+ lastEventId);
+ lastEventId++;
+ } else {
+ operation.op(true, permission);
+ }
+ }
+
@Override
public boolean onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
- if (requestCode == REQUEST_FINE_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;
- }
+ OperationOnPermission operation = operationsOnPermission.get(requestCode);
+ if (operation != null && grantResults.length > 0) {
+ operation.op(grantResults[0] == PackageManager.PERMISSION_GRANTED, permissions[0]);
return true;
}
return false;
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index 4e9af725..cbee0616 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -25,13 +25,13 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
- compileSdkVersion 30
+ compileSdkVersion 31
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.pauldemarco.flutter_blue_example"
minSdkVersion 19
- targetSdkVersion 30
+ targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 218143e6..8f06cdb0 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -9,7 +9,8 @@
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
- android:windowSoftInputMode="adjustResize">
+ android:windowSoftInputMode="adjustResize"
+ android:exported="true">