I'm mantaining an app that runs on an Android 6.0 tablet that uses a Zebra iMZ220 bluetooth printer. The app requires the printer to be paired to the tablet, of course.
From time to time, the printer appears to receive a new pairing request from the tablet to which it is already paired, with the end result of breaking the bond.
I've been unable to determine the exact cause of it, for there are no errors in the log and it appears to happen randomly.
I've found these lines which I think is supposed to pair the printer:
Method method = device.getClass().getMethod("createBond", (Class[]) null);
method.invoke(device, (Object[]) null);
in the sequence
String printerMac = settings.getString("printerMac","");
if (!stampante.isEmpty()) {
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(stampante);
try {
Method method = device.getClass().getMethod("createBond", (Class[]) null);
method.invoke(device, (Object[]) null);
} catch (Exception e) {
e.printStackTrace();
}
}
Could it, for some reason, remove the bond on the printer even though it is supposed to do the opposite?
I couldn't reproduce this error on a Android 8 device.
From my experience (since i am also developing and maintaining an app that connects to a bluetooth printer) the pairing between the device and the bluetooth device can sometimes be lost although it is really rare. It is a bug that spreads amongst various devices and Android versions.
What i ended up doing is accept the possibility that it might sometime happen and implement a broadcast receiver to be notified that the pairing has been lost. At that time i create the bond again programmatically and also connect the printer.
When trying to create the bond, a default dialog will appear for the user to input the PIN the printer uses.However, since i know the PIN, i input this PIN progrmmatically so the dialog just appears for a split second and then dissapears.
After months battling with this issue this ended up to be the most elegant solution i could find.Hope it helps you as well.
In onCreate create the filter and register the receiver:
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
registerReceiver(mReceiver, filter);
Method to call when you want to pair with the device:
private void pairDevice(BluetoothDevice device) {
try {
//Log.d("AutoPairing", "Start Pairing... with: " + device.getName());
device.createBond();
Log.d("AutoPairing", "Pairing finished.");
} catch (Exception e) {
Log.e("AutoPairing", e.getMessage());
}
}
Broadcast Receiver :
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
}else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
}else if (BluetoothDevice.ACTION_FOUND.equals(action)){
}else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
}else if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
try {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// If you want to auto_insert the pin, uncomment the following lines
//String PinNew = "HereIsThePinThePrinterUses";
//device.setPin(PinNew.getBytes());
} catch (Exception e) {
Log.e("AutoPairing", "Error occurs when trying to auto pair");
e.printStackTrace();
}
}else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBondState() == 12) {
// Pairing was succesful. Do what you would normally do after that. Perhaps connect the printer now.
} else {
// Pairing was unsuccesful. **This is also what get's triggered when the pairing is getting lost**. Let's pair the device then.
pairDevice(device);
}
}
}
};
Hope i am not forgetting something.
PM: All those empty if statements are there in case you want to connect the printer as well. If you do it some other way then you can delete them and remove those actions from the filter.
Related
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.
I'm a beginner in Android programming since I only started 3 months ago. I'm doing a project which connects the android app to arduino using bluetooth. I already have a code for the android app (bluetooth.adapter,sockets,.etc.). The code for connection is already working. One of the goal is for the android app to automatically input the password when pairing with the bluetooth device without asking user to input the PIN.
The old posts on this forum do not help much. (many suggested using insecure mode, but I do need secure mode, also in my case, the arduino is the server while cellphone app is the client, so the createInsecureRfcommSocketToServiceRecord() server method does not work for me)
I searched and found this in android developer site about bluetoothdevice class:
setPairingConfirmation(boolean confirm)
Confirm passkey for PAIRING_VARIANT_PASSKEY_CONFIRMATION pairing.
PAIRING_VARIANT_PIN = "The user will be prompted to enter a pin or an app will enter a pin for user".
PAIRING_VARIANT_PASSKEY_CONFIRMATION = "The user will be prompted to confirm the passkey displayed on the screen or an app will confirm the passkey for the user"
Seems using the code, the app will be the one to input the password and confirm
the password making it an "auto-connect" features but the android site does not give a sample code on how to use this. Does any of you have a sample code in using this or related process? I appreciate your help!
First to clarify, this solution is designed for newer version of API (15 or later?)
I found the answer written in another post (see Roldofo's answer in Here). Here is my reorganized answer with detailed code.
In a nutshell, you need to setup a broadcast receiver to trap the ACTION_PAIRING_REQUEST, and then programmatically pass the PIN and confirm.
Register a broadcast receiver:
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST);
getActivity().registerReceiver(mPairingRequestReceiver, filter);
The definition of the receiver:
private final BroadcastReceiver mPairingRequestReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
try {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int pin=intent.getIntExtra("android.bluetooth.device.extra.PAIRING_KEY", 1234);
//the pin in case you need to accept for an specific pin
Log.d(TAG, "Start Auto Pairing. PIN = " + intent.getIntExtra("android.bluetooth.device.extra.PAIRING_KEY",1234));
byte[] pinBytes;
pinBytes = (""+pin).getBytes("UTF-8");
device.setPin(pinBytes);
//setPairing confirmation if neeeded
device.setPairingConfirmation(true);
} catch (Exception e) {
Log.e(TAG, "Error occurs when trying to auto pair");
e.printStackTrace();
}
}
}
};
Then at your activity or fragment (wherever you want to initiate the pairing), you can call the following defined pairDevice() method to invoke pairing attempt (which will generate a ACTION_PAIRING_REQUEST)
private void pairDevice(BluetoothDevice device) {
try {
Log.d(TAG, "Start Pairing... with: " + device.getName());
device.createBond();
Log.d(TAG, "Pairing finished.");
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
I also faced the same problem and after all the research, I figured out the below solution.
(Tested and working!!!)
I am basically looking for a particular bluetooth device (I know MAC address) and pair with it once found. The first thing to do is to create pair request using boradcast receiver and handle the request as below.
IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST);
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
registerReceiver(broadCastReceiver,intentFilter);
You need to write the broadcastReceiver and handle it as below.
String BLE_PIN = "1234"
private BroadcastReceiver broadCastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action))
{
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
bluetoothDevice.setPin(BLE_PIN.getBytes());
Log.e(TAG,"Auto-entering pin: " + BLE_PIN);
bluetoothDevice.createBond();
Log.e(TAG,"pin entered and request sent...");
}
}
};
Voila! You should be able to pair to bluetooth device without ANY MANUAL INTERVENTION.
Hope this helps :-) Please make it right answer if it works for you.
Yes this possible to do by code
In you main activity add the following code
BluetoothReceiver myreceiver = new BluetoothReceiver();
var intentfilterparingrequest = new IntentFilter(BluetoothDevice.ActionPairingRequest);
RegisterReceiver(myreceiver, intentfilterparingrequest);
In your broadcast receiver write following code, if not create a new broadcast receiver
public class BluetoothReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
string BLE_PIN = "0000";
var action = intent.Action;
switch (action)
{
case BluetoothDevice.ActionPairingRequest:
BluetoothDevice bluetoothDevice =
(BluetoothDevice)intent.GetParcelableExtra(BluetoothDevice.ExtraDevice);
bluetoothDevice.SetPin(Encoding.ASCII.GetBytes(BLE_PIN));
bluetoothDevice.CreateBond();
break;
}
}
}
What I'm trying to do is to brin the dialog to input the PIN for a pairing process.
After I connect to a device, I receive a notification but the pairing dialog does not show up. I have to open it manually.
So far I tried the following methods which are called in the broadcast receiver when I get the PAIRING_REQUEST action:
public void pairDevice(BluetoothDevice device)
{
String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
Intent intent = new Intent(ACTION_PAIRING_REQUEST);
String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
intent.putExtra(EXTRA_DEVICE, device);
String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT";
int PAIRING_VARIANT_PIN = 0;
intent.putExtra(EXTRA_PAIRING_VARIANT, PAIRING_VARIANT_PIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
Which shows the dialog properly but after I input it, it does not pair my device.
I also tried this code:
public void pairDevice(BluetoothDevice device)
{
Intent intent = new Intent("android.bluetooth.device.action.PAIRING_REQUEST");
String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
intent.putExtra(EXTRA_DEVICE, device);
int PAIRING_VARIANT_PIN = 272;
intent.putExtra("android.bluetooth.device.extra.PAIRING_VARIANT", PAIRING_VARIANT_PIN);
sendBroadcast(intent);
}
Which crashes my app because it says I don't have permissions to send broadcast for PAIRING_REQUEST (even if I set both permissions BLUETOOTH and BLUETOOTH_ADMIN)
Please, I really need to show this dialog and much better if it is the default one. I am connecting to a BLE device, and after connected it requires a PIN for pairing and be able to modify some characteristics.
Your help would be much appreciated!
Thanks in advance!
Try to use new Android BluetoothDevice API: bluetoothdevice.createBond(). After you call this method, the system will invoke the pairing request dialog for you automatically. Then you can enter PIN in that pop up dialog.
Consider adding something like this in your code, where you want to start the pairing process:
private void pairDevice(BluetoothDevice device) {
try {
Log.d(TAG, "Start Pairing... with: " + device.getName());
device.createBond();
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
I had a similar issue, make sure your bluetooth user permissions are set within the manifest xml tag not under the application tag - in the AndroidManifest (that is). Hope this helps.
You can use connect method directly to pair the device.If the two devices have not been previously paired, then the Android framework will automatically show a pairing request notification or dialog to the user during the connection procedure. So when attempting to connect devices, your application does not need to be concerned about whether or not the devices are paired.
Try to use this for all API levels:
public static void pairDevice(BluetoothDevice device) {
try {
Method method = device.getClass().getMethod("createBond", (Class[]) null);
method.invoke(device, (Object[]) null);
//From API 19.
// device.createBond();
} catch (Exception e) {
e.printStackTrace();
}
}
I am new to Android Platform. I am working with an application requires integration of Bluetooth. The requirement is instead of manually connecting and disconnecting a Bluetooth headset(HSP profile),Connection and disconnection should be possible within the application.Is it possible to connect and disconnect the device in Android devices running OS 4.2 ,4.3 and 4.4.If any one has a solution for this issue,Please advise me for the same.
It is possible, but sometimes not that simple.
To connect, start by checking whether or not the device you are running on has BT support at all:
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter==null) {
// device not support BT
}
If not - gracefully disable the BT portion of your app and move on.
If supported, check whether or not it is currently enabled (remember, the user can
turn BT on & off as with other communication channels):
boolean isEnabled = bluetoothAdapter.isEnabled(); // Equivalent to: getBluetoothState() == STATE_ON
And, if not enabled, allow the user to turn it on by firing an ACTION_REQUEST_ENABLE intent:
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, ENABLE_BT_CODE);
Once you are clear in terms of availability, perform lookup for the specific device you aim for.
It is always a good idea to start with the bonded device list maintained by Android:
Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
for (BluetoothDevice device: pairedDevices) {
if (device is the one we look for) {
return device;
}
}
If not, you will need to issue a BT discovery command.
Discovery must never be performed on the UI thread, so please spawn a thread (use AsyncTask, Executer, etc. to do the work).
Discovery should not be performed when a BT connection operation is still taking place. The
impact on the device resources will be too high.
Start by setting your discovery receiver:
discoveryReceiver = new BroadcastReceiver() {
private boolean wasFound = false;
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
System.out.println(action);
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
discoveryStatus = STARTED;
}
else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
discoveryStatus = FINISHED;
}
else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device is what we look for) {
stopDiscovery(context);
}
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
filter.addAction(BluetoothDevice.ACTION_FOUND);
context.registerReceiver(discoveryReceiver, filter);
And follow with a start off command:
boolean started = bluetoothAdapter.startDiscovery(); //async call!
if (!started) {
// log error
}
Once you find your device, you will then need to create a dedicated BT socket:
BluetoothSocket clientSocket = null;
try {
if (secureMode == SECURE) {
clientSocket = device.createRfcommSocketToServiceRecord(serviceUuid);
}
else { // INSECURE
clientSocket = device.createInsecureRfcommSocketToServiceRecord(serviceUuid);
}
if (clientSocket == null) {
throw new IOException();
}
} catch (IOException e) {
// log error
}
Followed by connect command:
clientSocket.connect(context);
Once connect returns, you can transmit data back & forth the way you do with sockets and when done:
clientSocket.close(context);
The above depicts the general flow. In many cases your work will be harder:
You will use different socket generation methods for secure vs. insecure BT modes. You will use different
methods to interrogate the device for supported UUIDs. You may also sometimes have to resort to reflection to activate hidden services e.g. getUuids() for Android < ver 15. And the list goes on.
It makes sense, especially for a beginner, to use a tool for this job.
My favorite (I am biased, I wrote it..) is BTWiz which will encapsulate the above
flow from you and will also provide you with a simple interface for async IO. Feel free to try it out.
I'm working on an app that searches for discoverable devices and displays them as buttons.
When calling startDiscovery() I would say it works 30% of the time, based on the way I'm currently debugging it, with the BroadcastReceiver and ACTION_DISCOVERY_FINISHED.
I'm also using isDiscovering() to test if the startDiscovery() function is called but it returns false.
Is there a way to know if startDiscovery() is called successfully? And can you identify something in my code that would make it not fail?
Obs.: I have both BLUETOOTH AND BLUETOOTH_ADMIN permissions.
Here is my code:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scan);
mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String Address;
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Address = device.getAddress();
System.out.println("Found Address: " + Address ); //DEBUG
//Do something with Address
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
System.out.println("Discovery finished");
}
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
MainActivity.mBluetoothAdapter.startDiscovery();
if (MainActivity.mBluetoothAdapter.isDiscovering()) {
System.out.println("Discovering..."); //DEBUG
}
}
Although I have a few discoverable devices available, none of them trigger onReceive() with ACTION_FOUND
UPDATE: I went to "Scan" under Bluetooth Settings while the app was running and I could not scan for new devices. I disabled/enabled Bluetooth and returned to the app and the problem was resolved. I don't know if that indicates that the adapter is busy or halted somehow.
I confirm this issue.
On some telephones you just need to disable/active BT. You can doit programatically with
mBluetoothAdapter.disable();
mBluetoothAdapter.enable();
On some telephones its not enough ( Samsung S5 ). To detect it, I use timer, and if on end of timeout the change of BT broadcast state (BluetoothAdapter.ACTION_DISCOVERY_STARTED or BluetoothAdapter.ACTION_DISCOVERY_FINISHED ) wasnt received => its sign that BT is not working. Actually I show dialog which propose to user reboot the telephone.