Programmatically bonding to BLE device on Android - android

I'm writing an Android application in which I'd like to programmatically bond to a custom BLE device. I have the manual bonding working in which the user enters the PIN using the standard Android Bluetooth pairing dialog, but I have not been able to find any information on how to automatically bond a BLE device programatically, without user intervention. Is that possible? If so, what's the process?

I was able to make this work MOST OF THE TIME by registering a BroadcastReceiver to receive the BluetoothDevice.ACTION_BOND_STATE_CHANGED intent and then calling BluetoothDevice.setPin after receiving the BluetoothDevice.BOND_BONDING message. As is the case with most BLE things in Android, this seems to act slightly differently depending on the device and Android version. Unfortunately, I can't seem to stop Android from also receiving the bluetooth intent, so the PIN entry screen still pops up for a second before the bonding is completed.
private final BroadcastReceiver mReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
final String action = intent.getAction();
Logger("Broadcast Receiver:" + action);
if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED))
{
final int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
if(state == BluetoothDevice.BOND_BONDING)
{
Logger("Bonding...");
if (mDevice != null) {
mDevice.setPin(BONDING_CODE.getBytes());
Logger("Setting bonding code = " + BONDING_CODE);
}
}
else if(state == BluetoothDevice.BOND_BONDED)
{
Logger("Bonded!!!");
mOwner.unregisterReceiver(mReceiver);
}
else if(state == BluetoothDevice.BOND_NONE)
{
Logger("Not Bonded");
}
}
}
};

I managed to do this - see my answer here.
The TL;DR is: forget about ACTION_BOND_STATE_CHANGED; you don't need it. Instead listen to ACTION_PAIRING_REQUEST, and set the priority high. In the broadcast receiver when you get ACTION_PAIRING_REQUEST, call setPin() with your PIN and then abortBroadcast() to prevent the system showing the notification.

All you can do to avoid user interaction is to force Just Works pairing. To do that, program the peripheral to accept pairing with NoInputNoOutput IO Capability.

Related

Bluetooth devices found in an Android app, but not in a platform specific code in Flutter

I'm trying to scan, connect and receive data from a Bluetooth module. Everything works fine if I just use an android application. I can scan and find all nearby devices, connect to anyone (I'm only interested in my Bluetooth module) and I am able to read the test data that's being sent from the Bluetooth module.
The problem is that the application is being developed using Flutter. I used the same code from my Android application and linked it with Dart though the EventsChannel, but now I can only see fewer Bluetooth devices in the Flutter app and none of them is the Bluetooth Module I'm interested in. I'm new to Flutter and the platform specific coding, I can't understand why the same code behaves differently in different apps on same the hardware.
I've tested my code on Samsung S4 and S8 phones and the result is the same.
This is the code for the EventChannel for the discovery part:
new EventChannel(flutterEngine.getDartExecutor(), DISCOVER_CHANNEL).setStreamHandler(
new EventChannel.StreamHandler() {
#Override
public void onListen(Object args, EventChannel.EventSink events) {
Log.w(TAG, "Registering receiver");
discoverReceiver = DiscoverReceiver(events);
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(discoverReceiver, filter);
}
#Override
public void onCancel(Object args) {
Log.w(TAG, "Unregistering receiver");
unregisterReceiver(discoverReceiver);
discoverReceiver = null;
}
}
);
For now my discoverReceiver is a global BroadcastReceiver.
Below is the code for the Broadcastreceiver:
private BroadcastReceiver DiscoverReceiver(final EventSink events) {
return new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Discovery has found a device. Get the BluetoothDevice
// object and its info from the Intent.
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String deviceName = device.getName();
if (deviceName == null){
deviceName = "No Device Name";
}
events.success(deviceName);
Log.w(TAG, "Sending " + deviceName);
}
}
};
}
**I used the (Log.w(TAG, "Sending " + deviceName);) statement to see if events were being lost/dropped.
And below is how I receive it in Dart:
#override
void initState() {
super.initState();
devices.add(selectDevice);
discoverChannel.receiveBroadcastStream().listen(onEvent);
}
void onEvent(Object event) {
setState(() {
devices.add(event);
});
}
Below is the code in my Android app that can scan and find all devices in case you want to compare with the above:
private BroadcastReceiver DiscoverReceiver() {
return new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Discovery has found a device. Get the BluetoothDevice
// object and its info from the Intent.
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String deviceName = device.getName();
if (deviceName == null){
deviceName = "No Device Name";
}
devicelist.add(device);
devNames.add(deviceName);
arrayAdapter.add(deviceName);
arrayAdapter.notifyDataSetChanged();
Log.w(TAG, "Sending " + deviceName);
}
}
};
}
I'm not concerned with the last snippet but I just thought I'd show the complete flow. Snippet 2 is a copy of what I have in a stand-alone Android App and it scans and finds all devices, but once I use it in a Flutter App as native code for Android it stops finding the same number of devices, still finds some though and is very unreliable.
I have tried most of the Flutter bluetooth packages but none of them was what I was looking for and so I ended up going with Platform specific code, which worked fine until it was plugged to Flutter. I've read the documentation for Android development and the code above is mostly modified code from Android sample. I just can't figure out why the same code can find more devices as a stand-alone app versus using it as a native code for a flutter application if at the end it's being tested on the same hardware.
Any input will be appreciated!
Ok so I've finally found the solution. It is something to do with the Bluetooth's startDiscovery() running and doing its job properly but the events are captured a little later, something that the debugger would not be able to show.
So in my case, all devices were "discovered" but the flutter app starts capturing the events later so it only shows the last 1 or 2 devices that were discovered during the discovery.
I moved:
discoverChannel.receiveBroadcastStream().listen(onEvent);
From the initState() and into a function that is called after a button press, which makes sure everything has loaded before registering the broadcast receiver on the native side and the broadcast stream receiver on Dart's.
I'm still not sure exactly how to express this, but it's about the timing of registering the BroadcastReceiver on the native side and the receiveBroadcastStream on Dart's side.
Now it starts the discovery and captures the events properly, which shows the same number of devices found in an Android stand-alone app.
Hope this helps anyone who might face this odd issue in the future.

How long to wait to start BLE scan after it's enabled?

In Android, it is not possible to directly start a Bluetooth scan after you enabled Bluetooth on a device. If you would do this, you will get the following error:
D/BluetoothLeScanner: Scan failed, reason: app registration failed
The onScanFailed method will be called with error code 2 in the implemented ScanCallBack. Not much is known behind the reason of this error. But I found out the following (after a few hours of trying):
If you would wait ~5 seconds after you enabled Bluetooth and then start a scan (so not directly), it works. The scan starts with success. I came up with this temporary solution by the first answer of this question: Android BLE: "Scan failed, reason app registration failed for UUID"
As you can see that question is over a year old, however the questioner is using a separate Android library to handle BLE.
My question is, is there a better solution than the one I described above?
After enabling the Bluetooth adapter, it takes time for it to actually do all the initialization required, thus not allowing you to start scanning.
You can capture the broadcast event of the adapter's turn on event using the following broadcast receiver:
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) {
if (state == BluetoothAdapter.STATE_TURNING_OFF) {
onBluetoothDisabling();
} else {
onBluetoothDisabled();
}
requestEnableBluetooth();
} else if (state == BluetoothAdapter.STATE_ON) {
onBluetoothEnabled();
}
}
}
};
Also, register the broadcast receiver in your onCreate() method:
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBroadcastReceiver, filter);

Android - Correct way to detect disconnecting from a particular wifi ssid?

I've seen a couple of BroadcastReciever examples to detect wifi disconnects but none of them seem to work correctly (triggering twice for each disconnect for example) and none mention checking against an ssid, is this even possible?
So just to clarify, I want to detect disconnection from a particular ssid. An actual disconnect and not wifi being disabled on the device.
Thanks
EDIT: Re-opening as nothing works on both the devices we have to test.
NETWORK_STATE_CHANGED_ACTION was the answer in the end. The device having the problem registering this event started working when another app (which would also be listening for similar events) was uninstalled! No idea how or why an app could block events registering with another app. The final solution ended up being;
String action = intent.getAction();
if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION))
{
WifiManager manager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
NetworkInfo.State state = networkInfo.getState();
if(state == NetworkInfo.State.CONNECTED)
{
String connectingToSsid = manager.getConnectionInfo().getSSID().replace("\"", "");
WifiStateHistory.recordConnectedSsid(connectingToSsid);
//connected
}
if(state == NetworkInfo.State.DISCONNECTED)
{
if(manager.isWifiEnabled())
{
String disconnectedFromSsid = WifiStateHistory.getLastConnectedSsid();
//disconnected
}
}
}
Are you sure there are twice notification for same state? There are always two phase of disconnection:
WifiManager.WIFI_STATE_DISABLING
WifiManager.WIFI_STATE_DISABLED
My code to detect (and rebroadcast) connections and disconnects (not by disabling wifi) and including the SSID as an extra is as follows. Most of what I've read suggested using SUPPLICANT_CONNECTION_CHANGE_ACTION but this just did not work correctly, it would seemingly only fire when disabling/enabling wifi on my device (Nexus 4) and not during connections. The only problem is on first run of the app as it won't record the current ssid so doesn't know what the ssid of the network that has just been connected. Any ideas around this?
public class EventMapper extends BroadcastReceiver
{
private static String lastConnectedSsid = "";
#Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION))
{
SupplicantState state = intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE);
if(state == SupplicantState.COMPLETED)
{
WifiManager manager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
lastConnectedSsid = manager.getConnectionInfo().getSSID().replace("\"", "");
Intent newIntent = new Intent();
newIntent.setAction(Event.App_Event_WifiConnected.name());
newIntent.putExtra("App_Events_SSID", lastConnectedSsid);
context.sendBroadcast(newIntent);
}
if(state == SupplicantState.DISCONNECTED)
{
boolean wifiEnabled = ((WifiManager)context.getSystemService(Context.WIFI_SERVICE)).isWifiEnabled();
if(wifiEnabled)
{
Intent newIntent = new Intent();
newIntent.setAction(Event.App_Event_WifiDisconnected.name());
newIntent.putExtra("App_Events_SSID", lastConnectedSsid);
context.sendBroadcast(newIntent);
}
}
}
}
}
I got the same problem in some custom roms. I used "android.net.wifi.STATE_CHANGE" to listen the network change. In the receiver, I used "(NetworkInfo)intent.getParcelableExtra("networkInfo")).getState()" to get the network state. There are three states: DISCONNECTED, CONNECTING, CONNECTED. You can use DISCONNECTED to detect if the network is disconnected.
Please let me know if it works in your situation(HTC One X (4.1)).

Bluetooth ToggleButton not checking state

I am developing an app that is going to use a ToggleButton to enable and disable BlueTooth. I manage to make the ToggelButton turn on and off BlueTooth, but I can not make it check if BlueTooth is turned on and off. The problem is that if you turn BlueTooth on or off from another location, you may turn off BlueTooth when you actually want to turn it on. Here is my code so far:
public void onClick(View v) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if(adapter != null) {
if(adapter.getState() == BluetoothAdapter.STATE_ON) {
adapter.disable();
} else if (adapter.getState() == BluetoothAdapter.STATE_OFF){
adapter.enable();
} else {
//State.INTERMEDIATE_STATE;
}
}
}
How can I make it be checked when BlueTooth is on and unchecked when BlueTooth is off?
You will have to register a broadcast receiver with the following intent filter:
"android.bluetooth.intent.action.BLUETOOTH_STATE_CHANGED".
Whenever you receive this broadcast you should recheck for actual bluetooth state.
Check out Google's PowerWidget implementation.
The receiver with appropriate intent-filters is registered in the manifest.

Android: Creating a persistent event listener

I am trying to figure out how to implement an event listener (unsure if this is the proper term.) I want the service, once my app is launched, to listen for the phones power status. I am uncertain to as how android handles this situation so don't really know what to search for. I've been working with this code that uses a broadcast receiver:
BroadcastReceiver receiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
unregisterReceiver(this);
int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
if (plugged == BatteryManager.BATTERY_PLUGGED_AC) {
// on AC power
Toast.makeText(getApplicationContext(), "AC POWER", Toast.LENGTH_LONG).show();
} else if (plugged == BatteryManager.BATTERY_PLUGGED_USB) {
// on USB power
Toast.makeText(getApplicationContext(), "USB POWER", Toast.LENGTH_LONG).show();
startActivity(alarmClockIntent);
} else if (plugged == 0) {
Toast.makeText(getApplicationContext(), "On Battery", Toast.LENGTH_LONG).show();
} else {
// intent didnt include extra info
}
}
};
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(receiver, filter);
The code works fine. When I open my app it will toast what the current status of the phone power is.
Here is what I am trying to do:
When the user launches the app, it is effectively turning on the service
The user can go about using the phone, but once it is plugged in, my service will catch that and use the code above
How do I adapt this code to achieve the objectives above?
You could keep the listener on for the battery status by removing the line
unregisterReceiver(this);
This way, the app will continue to listen to power status change in the background even though that the app is not running in the foreground. Note that at some point, you might still want to unregister your receiver. You probably want to allow the user to control that via settings.
One other note, your code contains starting activity in the receiver in below code:
else if (plugged == BatteryManager.BATTERY_PLUGGED_USB) {
// on USB power
Toast.makeText(getApplicationContext(), "USB POWER", Toast.LENGTH_LONG).show();
startActivity(alarmClockIntent);
}
If your activity is in the background then it can't start another activity. See this SO Question - how to start activity when the main activity is running in background?, the accepted answer has suggestion on how to handle situation that requires starting activity from the background

Categories

Resources