Bluetooth device discovery in Android -- startDiscovery() - android

Goal: Build an Android app that discovers the names and addresses of BT devices within range and submits their values to a webservice. BT devices have not been previously bonded to the host device, I just want to poll everything as I walk about.
What I've done:
Pored over documentation.
Implemented a local instance of the host device's BT adapter.
Implemented a notification to enable BT if it isn't enabled.
Registered Broadcast Receivers and Intents to parse the ACTION_FOUNDs coming off of startDiscovery().
Registered BLUETOOTH and BLUETOOTH_ADMIN permissions in the manifest.
Things work (as tested with incremental console logging) up until startDiscovery().
Frustration:
startDiscovery() -- I suspect I am passing this in the wrong context. What context does this method need to be placed within to function properly?
If you have been able to get this method working, I would very much appreciate your wisdom.
UPDATE - here's a stripped down simplified version of the code that is causing me grief; this simplification recapitulates my error. This code runs, it throws no cat.log errors or other errors, it simply doesn't give any output.
package aqu.bttest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.Toast;
public class BT2Activity extends Activity {
private BluetoothAdapter mBTA;
private SingBroadcastReceiver mReceiver;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//register local BT adapter
mBTA = BluetoothAdapter.getDefaultAdapter();
//check to see if there is BT on the Android device at all
if (mBTA == null){
int duration = Toast.LENGTH_SHORT;
Toast.makeText(this, "No Bluetooth on this handset", duration).show();
}
//let's make the user enable BT if it isn't already
if (!mBTA.isEnabled()){
Intent enableBT = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBT, 0xDEADBEEF);
}
//cancel any prior BT device discovery
if (mBTA.isDiscovering()){
mBTA.cancelDiscovery();
}
//re-start discovery
mBTA.startDiscovery();
//let's make a broadcast receiver to register our things
mReceiver = new SingBroadcastReceiver();
IntentFilter ifilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, ifilter);
}
private class SingBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction(); //may need to chain this to a recognizing function
if (BluetoothDevice.ACTION_FOUND.equals(action)){
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a Toast
String derp = device.getName() + " - " + device.getAddress();
Toast.makeText(context, derp, Toast.LENGTH_LONG);
}
}
}
}

What context does this method need to be placed within to function properly.
To put it simply, you should use startDiscovery() when you want your application to discover local Bluetooth devices... for instance, if you wanted to implement a ListActivity that scans and dynamically adds nearby Bluetooth devices to a ListView (see DeviceListActivity).
Your usage of the startDiscovery() method should look something like this:
Define a class variable representing the local Bluetooth adapter.
BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter();
Check to see if your device is already "discovering". If it is, then cancel discovery.
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
Immediately after checking (and possibly canceling) discovery-mode, start discovery by calling,
mBtAdapter.startDiscovery();
Be very careful in general about accidentally leaving your device in discovery-mode. Performing device discovery is a heavy procedure for the Bluetooth adapter and will consume a lot of its resources. For instance, you want to make sure you check/cancel discovery prior to attempting to make a connection. You most likely want to cancel discovery in your onDestroy method too.
Let me know if this helped... and if you are still having trouble, update your answer with your logcat output and/or any error messages you are getting, and maybe I can help you out a bit more.

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.

Should NfcAdapter.enableReaderMode in foreground Activity override the intent tag dispatch system?

In Android 10 I noticed I get a Toast message from the OS stating "No supported application for this NFC tag" or "No supported app for this NFC tag" (depending on the device):
The weird thing is that I see the Toast while enableReaderMode is active in the foreground Activity. In all previous versions of Android, enableReaderMode would override the Android intent tag dispatch system. Is this a bug in Android 10?
I know enableForegroundDispatch also exists, and that API does seem to override the intent tag dispatch system even in Android 10. But I'd like to keep control over the NFC discovery sound which is only provided by enableReaderMode.
I also know that I can declare an intent-filter in my manifest to get rid of the Toast while continuing to use enableReaderMode, but that also has unintended side effects (e.g. my app could be launched while reading the NFC tag from the device home screen which I don't want).
Yes, it seems to be a bug as Android OS fires a new tagDiscovery event under the following conditions:
Use enableReaderMode instead of enableForegroundDispatch
Read or write a card
While the card is still in proximity call disableReaderMode.
Since this triggers an OS level event, it can pause focused activity, might show a toast screen or show related application select box.
To workaround the problem,
Workaround 1:
Try connecting the card in a loop until an IOException is fired. (which means card is not in proximity anymore)
Then call disableReaderMode
Cons: You may need to show a message to the user to remove the tag/card from the device proximity.
Workaround 2:
Use legacy enableForegroundDispatch / disableForegroundDispatch along with readerMode
Cons: Popup do not gets displayed, however tag discovery sound still gets triggered.
Both solutions do not need intent-filter to be defined.
Sample code that implements both workarounds is below.
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.widget.Toast;
import java.io.IOException;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity
{
private NfcAdapter nfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//Assuming nfc adapter is present and active, such checks are ignored for code clarity,
//production code must check for hardware nfc adapter existence
nfcAdapter = NfcAdapter.getDefaultAdapter(this); //Get the default NFC adapter from OS
//Additional initialization code
}
private void onTagDiscovered(Tag tag)
{
try
{
if (tag == null)
return;
//Assumption: We're using an NFC card that supports IsoDep
IsoDep iso = IsoDep.get(tag);
if (iso == null)
return;
iso.setTimeout(1000);
iso.connect();
//doCardReadWrite(iso);
iso.close();
//Workaround 1
//Wait until the card has been removed from the range of NFC
//Then finish the activity
while(true)
{
try
{
iso.connect();
Thread.sleep(100);
iso.close();
}
catch (IOException | InterruptedException e)
{
//On this example, once we're done with the activity, we want to close it
//then onPause event will call disableReaderMode, at that moment we don't want a card to be in proximity
onCardRemoved();
break;
}
}
//End of Workaround 1
}
catch (IOException e)
{
Toast.makeText(this, "Tag disconnected. Reason: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private void onCardRemoved()
{
this.finish();
}
#Override
protected void onResume()
{
super.onResume();
//Workaround 2
//Legacy nfc reader activation method, just enabled it but we won't use it
//(to fully support [e.g. OS version < v4.4.4] you must override onNewIntent(Intent intent) method as well)
//create intent/tag filters to be used with enableForegroundDispatch
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
0,
new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
0
);
IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
IntentFilter[] writeTagFilters = new IntentFilter[]{tagDetected};
nfcAdapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
//End of Workaround 2
//ReaderMode activation
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 1000);//Presence check interval
final int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK | NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS;
nfcAdapter.enableReaderMode(this, new NfcAdapter.ReaderCallback()
{
#Override
public void onTagDiscovered(Tag tag)
{
MainActivity.this.onTagDiscovered(tag);
}
}, READER_FLAGS, options);
}
#Override
protected void onPause()
{
nfcAdapter.disableReaderMode(this);
//Workaround 2
nfcAdapter.disableForegroundDispatch(this);
super.onPause();
}
}

Processing for Android 3, registerReceiver and getApplicationContext functions do not exist

I'm busy trying to piece together an app to transfer some data between an Android device and an Arduino.
The original code is taken from a tutorial and was written for Processing 2.0b, developing for API 10.
I am using Processing 3, developing for API 15.
This is the code:
/* DiscoverBluetooth: Written by ScottC on 18 March 2013 using
Processing version 2.0b8
Tested on a Samsung Galaxy SII, with Android version 2.3.4
Android ADK - API 10 SDK platform */
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.Toast;
import android.view.Gravity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
boolean foundDevice=false; //When this is true, the screen turns green.
//Get the default Bluetooth adapter
BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
/*The startActivityForResult() within setup() launches an
Activity which is used to request the user to turn Bluetooth on.
The following onActivityResult() method is called when this
Activity exits. */
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode==0){
if(resultCode == -1){
ToastMaster("Bluetooth has been switched ON");
} else {
ToastMaster("You need to turn Bluetooth ON !!!");
}
}
}
/* Create a Broadcast Receiver that will later be used to
receive the names of Bluetooth devices in range. */
BroadcastReceiver myDiscoverer = new myOwnBroadcastReceiver();
void setup(){
orientation(LANDSCAPE);
/*IF Bluetooth is NOT enabled, then ask user permission to enable it */
if (!bluetooth.isEnabled()) {
Intent requestBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(requestBluetooth, 0);
}
/*If Bluetooth is now enabled, then register a broadcastReceiver to report any
discovered Bluetooth devices, and then start discovering */
if (bluetooth.isEnabled()) {
registerReceiver(myDiscoverer, new IntentFilter(BluetoothDevice.ACTION_FOUND));
//Start bluetooth discovery if it is not doing so already
if (!bluetooth.isDiscovering()){
bluetooth.startDiscovery();
}
}
}
void draw(){
//Display a green screen if a device has been found
if(foundDevice){
background(10,255,10);
}
}
/* This BroadcastReceiver will display discovered Bluetooth devices */
public class myOwnBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String discoveredDeviceName = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
//Display the name of the discovered device
ToastMaster("Discovered: " + discoveredDeviceName);
//Change foundDevice to true which will make the screen turn green
foundDevice=true;
}
}
/* My ToastMaster function to display a messageBox on the screen */
void ToastMaster(String textToDisplay){
Toast myMessage = Toast.makeText(getApplicationContext(),
textToDisplay,
Toast.LENGTH_LONG);
myMessage.setGravity(Gravity.CENTER, 0, 0);
myMessage.show();
}
I'm getting compile errors on lines 57 and 93 stating:
The function "registerRevceiver(BroadcastReceiver, IntentFilter)" does not exist
The function "getApplicationContext()" does not exist
Does anyone have any ideas as to how to get this code example to work? I suspect it's an issue to do with the API level and some library mismatching of sorts.
Any help would be greatly appreciated!
Jono
Instead of getApplicationContext() use ActivityName.this.

Bluetooth on Android: Debuging startDiscovery()

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.

changing UI elements if app is in the foreground from a broadcast receiver

I Have a BroadcastReceiver set up to turn Bluetooth on and off according to power state (when plugged in, bluetooth is on, unplugged, bluetooth is off). This is working just fine (yay!). however, my very simple app has a single button, which also turns Bluetooth on and off, and has the text "Bluetooth on" or "Bluetooth Off", as applicable. I would like to update this single button, BUT, I only have to update it if the app is in the foreground.
Inside onResume on m,y main activity, I'm calling my updateUI method, which checks the Bluetooth state, and updates the button accordingly. however, that only applies if the program was open and in the background, and is resumed, NOT if i'm in the program while plugging/unplugging the power.
I created a new activity (CheckIfAppIsRunningActivity.java) with this code which is supposed to check if my app is running in the foreground, and if so, take it to the activity (BluetoothOnActivity) which will update the button:
package vermel.BluetoothOn;
import java.util.List;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class CheckIfAppIsRunningActivity extends Activity{
public void onCreate() {
checkStatus();
}
private BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Intent it = new Intent();
it.setClassName("vermel.BluetoothOn", "vermel.BluetoothOn.BluetoothOnActivity");
context.startActivity(it);
}
};
public void checkStatus() {
ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningProcInfo = activityManager.getRunningAppProcesses();
for(int i = 0; i < runningProcInfo.size(); i++){
if(runningProcInfo.get(i).processName.equals("vermel.BluetoothOn")) {
if (runningProcInfo.get(i).lru == RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
//start activity
/* Intent it = new Intent();
it.setClassName("vermel.BluetoothOn", "vermel.BluetoothOn.BluetoothOnActivity");
context.startActivity(it); */
}
}
}
}
}
and i'm pointing to it from my broadcast receiver:
package vermel.BluetoothOn;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Button;
import android.widget.Toast;
public class BTDetector extends BroadcastReceiver {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
public void onReceive(Context context , Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_POWER_CONNECTED)) {
if (!mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.enable();
//TODO if app is open, change text on button to on
//Toast.makeText(context, "turned on bluetooth", Toast.LENGTH_LONG).show();
Intent i = new Intent();
i.setClassName("vermel.BluetoothOn", "vermel.BluetoothOn.CheckIfAppIsRunningActivity");
context.startActivity(i);
}
} else if (action.equals(Intent.ACTION_POWER_DISCONNECTED)) {
if (mBluetoothAdapter.isEnabled()){
mBluetoothAdapter.disable();
//TODO if app is open, change text on button to off
//Toast.makeText(context, "turned off bluetooth", Toast.LENGTH_LONG).show();
Intent i = new Intent();
i.setClassName("vermel.BluetoothOn", "vermel.BluetoothOn.CheckIfAppIsRunningActivity");
context.startActivity(i);
}
}
}
}
a few things: yes, I know i'm not supposed to use .enable() without user permission. in a weird way, plugging in the phone IS my user permission, since this is ALL that this app does, so, it's not sneaky, since you know what you're getting when you're installing the app.
The commented stuff is mostly things i've tried in vain..
I'm very open to the fact that i'm making this WAY harder than I need to...
so, as i said, it does turn Bluetooth on and off beautifully, but simply crashes after that. I can't debug it, since the emulator doesn't have Bluetooth , and i'm disconnecting the phone to get the crash result, so, it's not logging anything, since it's now connected...
I'm new in both Java and Android, and would appreciate a bit of patience. I try reading the official android documentation, but that's like chinese to me... so, an extended explanation would be great...
Thanks for reading!

Categories

Resources