Skip to content

Implement method to turn on the Bluetooth module #921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener;
import io.flutter.plugin.common.PluginRegistry.ActivityResultListener;


/** FlutterBluePlugin */
public class FlutterBluePlugin implements FlutterPlugin, ActivityAware, MethodCallHandler, RequestPermissionsResultListener {
public class FlutterBluePlugin implements FlutterPlugin, ActivityAware, MethodCallHandler, RequestPermissionsResultListener, ActivityResultListener {
private static final String TAG = "FlutterBluePlugin";
private Object initializationLock = new Object();
private Context context;
Expand All @@ -78,6 +79,7 @@ public class FlutterBluePlugin implements FlutterPlugin, ActivityAware, MethodCa
private Activity activity;

private static final int REQUEST_FINE_LOCATION_PERMISSIONS = 1452;
private static final int REQUEST_ENABLE_BT = 1453;
static final private UUID CCCD_ID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private final Map<String, BluetoothDeviceCache> mDevices = new HashMap<>();
private LogLevel logLevel = LogLevel.EMERGENCY;
Expand Down Expand Up @@ -158,9 +160,11 @@ private void setup(
if (registrar != null) {
// V1 embedding setup for activity listeners.
registrar.addRequestPermissionsResultListener(this);
registrar.addActivityResultListener(this);
} else {
// V2 embedding setup for activity listeners.
activityBinding.addRequestPermissionsResultListener(this);
activityBinding.addActivityResultListener(this);
}
}
}
Expand Down Expand Up @@ -236,6 +240,24 @@ public void onMethodCall(MethodCall call, Result result) {
break;
}

case "enable":
{
if (!mBluetoothAdapter.isEnabled()) {
boolean ask = (boolean)(call.arguments);
if (ask) {
activity.startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE_BT);
result.success(null);
}
else {
result.success(mBluetoothAdapter.enable());
}
}
else {
result.success(true);
}
break;
}

case "startScan":
{
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
Expand Down Expand Up @@ -652,6 +674,15 @@ public boolean onRequestPermissionsResult(
return false;
}

@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == REQUEST_ENABLE_BT) {
channel.invokeMethod("EnableResult", resultCode == Activity.RESULT_OK);
return true;
}
return false;
}

private BluetoothGatt locateGatt(String remoteId) throws Exception {
BluetoothDeviceCache cache = mDevices.get(remoteId);
if(cache == null || cache.gatt == null) {
Expand Down
77 changes: 14 additions & 63 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,78 +10,29 @@ project 'Runner', {
'Release' => :release,
}

def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
end
generated_key_values
end

target 'Runner' do
# Flutter Pod

copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.

generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];

unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

# Plugin Pods
flutter_ios_podfile_setup

# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
target 'Runner' do
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
install! 'cocoapods', :disable_input_output_paths => true

post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
flutter_additional_ios_build_settings(target)
end
end
12 changes: 3 additions & 9 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
PODS:
- e2e (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_blue (0.0.1):
- Flutter
Expand All @@ -11,7 +9,6 @@ PODS:
- Protobuf (3.11.4)

DEPENDENCIES:
- e2e (from `.symlinks/plugins/e2e/ios`)
- Flutter (from `Flutter`)
- flutter_blue (from `.symlinks/plugins/flutter_blue/ios`)

Expand All @@ -20,19 +17,16 @@ SPEC REPOS:
- Protobuf

EXTERNAL SOURCES:
e2e:
:path: ".symlinks/plugins/e2e/ios"
Flutter:
:path: Flutter
flutter_blue:
:path: ".symlinks/plugins/flutter_blue/ios"

SPEC CHECKSUMS:
e2e: 967b9b1fc533b7636a3b7a719f840c27f301fe1f
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
flutter_blue: eeb381dc4727a0954dede73515f683865494b370
Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7

PODFILE CHECKSUM: 3dbe063e9c90a5d7c9e4e76e70a821b9e2c1d271
PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d

COCOAPODS: 1.9.1
COCOAPODS: 1.10.1
40 changes: 23 additions & 17 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,29 @@ class BluetoothOffScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightBlue,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.bluetooth_disabled,
size: 200.0,
color: Colors.white54,
),
Text(
'Bluetooth Adapter is ${state != null ? state.toString().substring(15) : 'not available'}.',
style: Theme.of(context)
.primaryTextTheme
.subhead
?.copyWith(color: Colors.white),
),
],
body: InkWell(
onTap: () {
FlutterBlue.instance.enable(ask: true)
.then((value) => print('Bluetooth enable returned $value'));
},
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.bluetooth_disabled,
size: 200.0,
color: Colors.white54,
),
Text(
'Bluetooth Adapter is ${state != null ? state.toString().substring(15) : 'not available'}.',
style: Theme.of(context)
.primaryTextTheme
.subhead
?.copyWith(color: Colors.white),
),
],
),
),
),
);
Expand Down
8 changes: 8 additions & 0 deletions ios/Classes/FlutterBluePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
} else {
result(@(NO));
}
} else if([@"enable" isEqualToString:call.method]) {
// Enabling Bluetooth programmatically is not possible on iOS.
// If it is already enabled, we still consider it successful to match Android behaviour
if(self.centralManager.state == CBManagerStatePoweredOn) {
result(@(YES));
} else {
result(@(NO));
}
} else if([@"startScan" isEqualToString:call.method]) {
// Clear any existing scan results
[self.scannedPeripherals removeAllObjects];
Expand Down
22 changes: 22 additions & 0 deletions lib/src/flutter_blue.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,28 @@ class FlutterBlue {
.map((s) => BluetoothState.values[s.state.value]);
}

/// Turn on Bluetooth module.
///
/// On Android, this is done silently by default (needs `android.permission.BLUETOOTH_ADMIN` permission).
/// If [ask] is true, a system dialog asking for permission is shown.
///
/// On iOS, the functionality is not supported by the operating system and [enable] will return false if the Bluetooth is turned off
/// and true if it is on.
Future<bool> enable({bool ask = false}) async {
var result = await _channel
.invokeMethod<bool>('enable', ask);
if (result == null) {
return FlutterBlue.instance._methodStream
.where((m) => m.method == "EnableResult")
.map((m) => m.arguments)
.first
.then<bool>((c) {
return (c);
});
}
return result;
}

/// Retrieve a list of connected devices
Future<List<BluetoothDevice>> get connectedDevices {
return _channel
Expand Down