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">