How to connect to Bluetooth SPP using dot42? - android

After reading the dot42 comments and trolling Java examples I managed to setup a Bluetooth connection but fail to open the connection. I cannot determine the problem. I followed the docs step by step.
My target device is a HTC Explorer running on 2.3 Gingerbread. Here is my code.
//Target 2.3 (Gingerbread)
[assembly: Application("dot42Application1")]
[assembly: UsesPermission(Android.Manifest.Permission.BLUETOOTH)]
[assembly: UsesPermission(Android.Manifest.Permission.BLUETOOTH_ADMIN)]
namespace dot42Application1
{
[Activity]
public class MainActivity : Activity
{
private TextView txStatus;
protected override void OnCreate(Bundle savedInstance)
{
base.OnCreate(savedInstance);
SetContentView(R.Layouts.MainLayout);
// Find UI controls
txStatus = FindViewById<TextView>(R.Ids.txStatus);
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
var bt = BluetoothAdapter.GetDefaultAdapter();
if (bt != null) //If device has not Bluetooth this will be null
{
if (bt.IsEnabled()) //Is Bluetooth device enabled?
{
var BT_My_Addr = bt.Address; //Get the devices MAC
var BT_Bonded = bt.GetBondedDevices().ToList(); //Get a list of bonded devices- I bonded to a BT2TTL Board earlier.
txStatus.Text = BT_My_Addr + System.Environment.NewLine; //Shows my MAC on screen.
string BT_Remote_Address = string.Empty;
foreach (var BTDevice in BT_Bonded) //Just searchging for string in bonded list
{
if (BTDevice.Name.Contains("linvor"))
{
BT_Remote_Address = BTDevice.Address;
}
}
//Gets remote device
var BT_Remote_Device = bt.GetRemoteDevice(BT_Remote_Address);
//Create a RFCOMM Socket to remote device using popular UUID ofr BT Serial boards
var BTsocket = BT_Remote_Device.CreateInsecureRfcommSocketToServiceRecord(Java.Util.UUID.FromString("00001101-0000-1000-8000-00805F9B34FB"));
//Call anyway to make sure there is no discvoerry in the backgorund. It slows stuff down.
bt.CancelDiscovery();
//Exception here? Dont know why :(
BTsocket.Connect();
//Suppsoed to dump 0 to 99999 to my listening serial device but I never get this far.
var BT_Out = BTsocket.GetOutputStream();
for (int i = 0; i < 99999; i++)
{
BT_Out.Write(Encoding.ASCII.GetBytes(i.ToString()));
}
}
else
{
txStatus.Text = "Bluetooth is disabled :(";
}
}
}
}
And this is what it shows after the socket creation
and the error...
What am I doing wrong? :(

I seem to have solved the problem by analysing various code snippets on the internet. I think the problem was trying to do everything in the OnCreate method. The steps I followed are the following:
Created a button on the main view (MainActivity.xml) and attached a onClick method.
Moved all the code OUT of the OnCreate method. (I think this allows the application to fully initialise.) Created an event handler for the button with two methods.
The two methods are the same as the code I posted in my original question. Just they are separated out and called when the user clicks the button.
findBT() Gets the default adapter. Checks if Bluetooth is enabled if not does the intent filter. Or if it is it will cycle through the bonded list and match a device name and store the BluetoohDevice in a variable. This is another thing that is different from my code. I do not use GetRemoteDevice I just assign the device from the BondedList to my global variable.
openBT() creates the RFCOMM socket (this did not work with unsecure - it threw an exception but using the secure method worked!)
You have to pair to the remote device using the Androids Bluetooth control panel. This code will not scan or connect to devices that are not paired. It will just throw null exceptions.
Also I left the target SDK 2.3.x but I am using the 4.x API.
-Disclosure. I am not a seasoned Android developer and just learning about the life cycle of Java applications in the Android context. I hope this can help other C# developers trying to do the same.

Related

Flutter/Dart UDP multicast send from iOS not receiving on Android

With this setup, I've been able to get two android phones to send and receive UDP broadcasts. I can also use this setup to send a UDP broadcast from a physical Android device to an iPhone.
However, my problem is that it doesn't seem to work the other way around. The send function is ran on the iPhone, and the receive function is being run on the Android phone. The Android phone never gets the broadcast. It seems like something is wrong with the iPhone's sending function. Here's the setup:
The Android side that has worked for me before:
const port = 37069;
const address = '224.0.0.1';
void receive() async {
final socket = await RawDatagramSocket.bind(address, port);
socket.multicastHops = 1;
socket.broadcastEnabled = true;
socket.writeEventsEnabled = true;
socket.listen((RawSocketEvent event) {
print("still listening...");
final packet = socket.receive();
print("The packet was $packet");
print("It came from ${packet?.address}");
});
}
and this is the iPhone side, that seems to be the problem. I'm not getting errors, so I'm wondering if there are any permissions in the Info.plist file that need to be added?
void broadcast() {
// for the iphone
RawDatagramSocket.bind(address, port).then((RawDatagramSocket socket) {
socket.multicastLoopback = false;
socket.broadcastEnabled = true;
socket.readEventsEnabled = true;
for (int i = 0; i < 150; i++) {
socket.send("Sent #$i".codeUnits, InternetAddress(address), port);
print("sent $i");
}
socket.close();
});
}
I've tested this same setup in my project, and it has worked in the following situations:
Android -> Android
Android -> iOS
but, iOS -> Android doesn't work. When I run the app, I can see that the iPhone is indeed sending the data, but the Android isn't receiving anything. Is the Android side the problem? What am I doing wrong?
I ended up using a package called Bonsoir to achieve what I wanted to.
It lets you broadcast and receive network services, and I'm pretty sure its the same underlying technology as household programs like Airplay and Google Casting. It's also very reliable and simple to use.
To send a certain string, I passed in a string argument in the form of a dictionary into the attributes attribute within the BonsoirService class.
The package can be found here.

In Android, how to get the profile of a connected bluetooth device?

Following a lot of answers here, I am able to build the list of connected bluetooth devices with the help of a BroadcastReceiver. Now my question is how do I know which device supports which profile. I want to be able to pick the devices based on the profile, for example, get a list of currently connected devices and their profile, and pick one of them. I don't see how I can get such info if I have the instance of BluetoothDevice.
On this page there are some codes illustrating how to work with a bluetooth headset profile: http://developer.android.com/guide/topics/connectivity/bluetooth.html#Profiles. But it doesn't solve my problem. If you think I am missing anything, please help me and point it out.
Thanks a lot in advance.
I've run into the same problem. It doesn't appear that you can get the available profiles from the BluetoothDevice class.
But there is a long way around by getting a List of BluetoothDevices from the getDevicesMatchingConnectionStates method in the BluetoothProfile class.
For example if you want to find which BluetoothDevices support A2DP, first create a custom BluetoothProfile.ServiceListener
public class cServiceListener implements BluetoothProfile.ServiceListener {
private static final int[] states={ BluetoothProfile.STATE_DISCONNECTING,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING};
#Override
public void onServiceConnected(int profile, BluetoothProfile bluetoothProfile) {
List<BluetoothDevice> Devices=bluetoothProfile.getDevicesMatchingConnectionStates(states);
for (BluetoothDevice loop:Devices){
Log.i("myTag",loop.getName());
}
}
#Override
public void onServiceDisconnected(int profile) {
}
}
Then attach it to the profile you want to check, in this example A2DP
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
cServiceListener mServiceListener=new cServiceListener();
mBluetoothAdapter.getProfileProxy(thisContext,mServiceListener, BluetoothProfile.A2DP);
This will logcat all the bluetooth devices that support A2DP which are in the requested states. In this example it includes all devices which are currently connected and previously paired devices which are disconnected.
Looking at the Android source code, you can guess which profiles are available for a device by looking at its UUIDs, and then connect each profile one by one.
Step 0 : Copy the _PROFILE_UUIDS constants from there : https://android.googlesource.com/platform/packages/apps/Settings/+/9ad703cdb9a8d0972c123b041d18aa7bbeb391a4/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
Step 1 : get your BluetoothDevice, via scanning for instance. Assure that it's properly bonded.
Step 2 : register a BroadcastReceiver for the android.bluetooth.BluetoothDevice.ACTION_UUID action intent
Step 3 : on your device, call the fetchUuidsWithSdp method
Step 4 : you will recieve a ACTION_UUID broadcast : in the onReceive method you can unregister the receiver, and get the list of profiles like so :
ArrayList<Integer> profiles = new ArrayList<>();
ParcelUuid[] uuids = device.getUuids();
if (BluetoothUuid.containsAnyUuid(uuids, HEADSET_PROFILE_UUIDS))
{
profiles.add(BluetoothProfile.HEADSET);
}
if (BluetoothUuid.containsAnyUuid(uuids, A2DP_PROFILE_UUIDS))
{
profiles.add(BluetoothProfile.A2DP);
}
if (BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS))
{
//OPP doesn't have any BluetoothProfile value
}
if (BluetoothUuid.containsAnyUuid(uuids, HID_PROFILE_UUIDS))
{
//You will need system privileges in order to use this one
profiles.add(BluetoothProfile.INPUT_DEVICE);
}
if (BluetoothUuid.containsAnyUuid(uuids, PANU_PROFILE_UUIDS))
{
profiles.add(BluetoothProfile.PAN);
}
Step 5 : get the proxies for the profiles, one by one :
for (int profile : profiles)
{
if (!adapter.getProfileProxy(context, listener, profile))
{
//Do something
}
}
Step 6 : do anything with each proxy retrieved in the onServiceConnected method of your listener. You can access the connect method using relfection.

Android KitKat Bluetooth connect

Am i the only one having problems connecting to the bluetooth with startBluetoothSco? This works fine in all versions of Android except 4.4.2 (kitkat). Any suggestions? And yes, I have verified that I am connected to Bluetooth before I call this. Did something changed in 4.4.2?
Here is my code:
am = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
am.setMode(AudioManager.MODE_IN_CALL);
am.setBluetoothScoOn(true);
am.startBluetoothSco();
Following your suggestion i did the following, but this is driving me nuts! What am I doing wrong. I have the listener in my MainActivity as follows...
private final BluetoothHandler.Listener mBluetoothListener = new BluetoothHandler.Listener() {
#Override
public void onConnectionComplete() {
final BluetoothHandler bluetoothHandler = mBluetoothHandler;
if (bluetoothHandler != null) {
am.setMode(AudioManager.MODE_IN_COMMUNICATION);
}
}
};
Then in my OnCreate I initialize the BluetoothHandler
if(mBluetoothHandler == null){
mBluetoothHandler = new BluetoothHandler(5000, mBluetoothListener);
} else {
mBluetoothHandler.stopSco();
mBluetoothHandler.stop();
mBluetoothHandler = null;
}
if (!mBluetoothHandler.isAudioConnected()) {
mBluetoothHandler.start(mContext);
}
The problem I'm having is that the listener doesn't detect when a BT device connects or even says that one is connected. Any suggestions? I appreciate your help...
The functionality of startBluetoothSco() changed between API 17 and API 18. In API 17, this function initiates a virtual call via SCO. In API 18, the function opens a raw SCO link. Some Bluetooth units will only respond to a virtual call.
Unfortunately it doesn't seem that Google have given us an option of choosing whether to open a virtual call or raw link so if you require a virtual call, you will need to ensure your app is built with API 17.
From the Android Developer reference:
"NOTE: up to and including API version JELLY_BEAN_MR1, this method
initiates a virtual voice call to the bluetooth headset. After API
version JELLY_BEAN_MR2 only a raw SCO audio connection is
established."
http://developer.android.com/reference/android/media/AudioManager.html#startBluetoothSco()
Which device do you use? I know that there is sometimes a Bluetooth problem with nexus devices (nexus 5 with kit at also) as described here:
http://www.androidpolice.com/2013/12/28/bug-watch-many-nexus-devices-still-suffer-from-assorted-bluetooth-issues/
Had the same problem with an app I was developming. Upong updating my phone to KitKat, the SCO connection to my car stopped working.
I finally come up with a solultion using a somehow private api in the bluetooth headset profile, while keeping my code compatible with API 19.
Fist I'm checking if current version is API 17, in that case, I use the standard startBluetoothSco from AudioManager, if not the case, I get the current BluetoothHeadset profile and use the following method to create the virtual call sco link (I can't take credit for this, I found it in the Google TalkBack application):
class BluetoothHeadsetCompatWrapper {
private static final Class<?> CLASS_BluetoothHeadset = BluetoothHeadset.class;
private static final Method METHOD_startScoUsingVirtualVoiceCall = CompatUtils.getMethod(
CLASS_BluetoothHeadset, "startScoUsingVirtualVoiceCall", BluetoothDevice.class);
private static final Method METHOD_stopScoUsingVirtualVoiceCall = CompatUtils.getMethod(
CLASS_BluetoothHeadset, "stopScoUsingVirtualVoiceCall", BluetoothDevice.class);
private final BluetoothHeadset mHeadset;
public BluetoothHeadsetCompatWrapper(BluetoothHeadset headset) {
mHeadset = headset;
}
public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
return (Boolean) CompatUtils.invoke(mHeadset, false, METHOD_startScoUsingVirtualVoiceCall,
device);
}
public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
return (Boolean) CompatUtils.invoke(mHeadset, false, METHOD_stopScoUsingVirtualVoiceCall,
device);
}
}
I found this solution when I was looking into the BluetoothHeadset code and found out the method actually exists, but it's hidden to the compiler https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/bluetooth/BluetoothHeadset.java
I know my anwser may be late, but I posted anyway to help others.

How to auto-accept Wi-Fi Direct connection requests in Android

I have 2 Android devices using WiFi Direct. On one device I can get information about the other device using the WifiP2pManager class, and request a connection to the other device. However when I request a connection, the other device pops up a little window and asks the user if they want to accept the connection request.
Is it possible to auto-accept these connection requests? I.E to be able to connect to the other device without user confirmation?
It can be easily done with the help of Xposed framework. You just need to replace the single method inside one of android java classes (see the link from snihalani's answer). But of course to use Xposed your device must be rooted. The main idea can be expressed in the following code (using Xposed)
#Override
public void handleLoadPackage(LoadPackageParam lpparam) {
try {
Class<?> wifiP2pService = Class.forName("android.net.wifi.p2p.WifiP2pService", false, lpparam.classLoader);
for (Class<?> c : wifiP2pService.getDeclaredClasses()) {
//XposedBridge.log("inner class " + c.getSimpleName());
if ("P2pStateMachine".equals(c.getSimpleName())) {
XposedBridge.log("Class " + c.getName() + " found");
Method notifyInvitationReceived = c.getDeclaredMethod("notifyInvitationReceived");
final Method sendMessage = c.getMethod("sendMessage", int.class);
XposedBridge.hookMethod(notifyInvitationReceived, new XC_MethodReplacement() {
#Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
final int PEER_CONNECTION_USER_ACCEPT = 0x00023000 + 2;
sendMessage.invoke(param.thisObject, PEER_CONNECTION_USER_ACCEPT);
return null;
}
});
break;
}
}
} catch (Throwable t) {
XposedBridge.log(t);
}
}
I tested it on SGS4 stock 4.2.2 ROM and it worked.
I guess the same could be done with the help of Substrate for android.
From my current understanding of the API, You cannot really accept connections automatically without user's intervention. You can initiate a connection, that doesn't require user intervention. If both of your devices are mobile devices, you will have to accept connection request on one end.
I have put this as a feature request in android project hosting.
You can monitor their response here: https://code.google.com/p/android/issues/detail?id=30880
Based on the comments, do you really need to connect to the devices if you just want to track and log the vehicles around you ?
I don't know the scope of the project, but you could simply use the WifiP2pDeviceList that you get when you request the peers in the WifiP2pManager. You could get the list of the devices (~= vehicles) around you and could log this.
Connection is useful if you want to send more detailed information I guess.
If you can modify the framework, you can ignore the accept window and direct send the "PEER_CONNECTION_USER_ACCEPT".
Base on Android 5.0, "frameworks/opt/net/wifi/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java".
You must find the "notifyInvitationReceived", and modify to ...
private void notifyInvitationReceived() {
/*Direct sends the accept message.*/
sendMessage(PEER_CONNECTION_USER_ACCEPT);
/*
... old code
*/
}

How to prevent name caching and detect bluetooth name changes on discovery

I'm writing an Android app which receives information from a Bluetooth device. Our client has suggested that the Bluetooth device (which they produce) will change its name depending on certain conditions - for the simplest example its name will sometimes be "xxx-ON" and sometimes "xxx-OFF". My app is just supposed to seek this BT transmitter (I use BluetoothAdapter.startDiscovery() ) and do different things depending on the name it finds. I am NOT pairing with the Bluetooth device (though I suppose it might be possible, the app is supposed to eventually work with multiple Android devices and multiple BT transmitters so I'm not sure it would be a good idea).
My code works fine to detect BT devices and find their names. Also, if the device goes off, I can detect the next time I seek, that it is not there. But it seems that if it is there and it changes name, I pick up the old name - presumably it is cached somewhere. Even if the bluetooth device goes off, and we notice that, the next time I detect it, I still see the old name.
I found this issue in Google Code: here but it was unclear to me even how to use the workaround given ("try to connect"). Has anyone done this and had any luck? Can you share code?
Is there a simple way to just delete the cached names and search again so I always find the newest names? Even a non-simple way would be good (I am writing for a rooted device).
Thanks
I would suggest 'fetchUuidsWithSdp()'. It's significance is that, unlike the similar getUuids() method, fetchUuidsWithSdp causes the device to update cached information about the remote device. And I believe this includes the remote name as well as the SPD.
Note that both the methods I mentioned are hidden prior to 4.0.3, so your code would look l ike this:
public static void startServiceDiscovery( BluetoothDevice device ) {
// Need to use reflection prior to API 15
Class cl = null;
try {
cl = Class.forName("android.bluetooth.BluetoothDevice");
} catch( ClassNotFoundException exc ) {
Log.e(CTAG, "android.bluetooth.BluetoothDevice not found." );
}
if (null != cl) {
Class[] param = {};
Method method = null;
try {
method = cl.getMethod("fetchUuidsWithSdp", param);
} catch( NoSuchMethodException exc ) {
Log.e(CTAG, "fetchUuidsWithSdp not found." );
}
if (null != method) {
Object[] args = {};
try {
method.invoke(device, args);
} catch (Exception exc) {
Log.e(CTAG, "Failed to invoke fetchUuidsWithSdp method." );
}
}
}
}
You'll then need to listen for the BluetoothDevice.ACTION_NAME_CHANGED intent, and extract BluetoothDevice.EXTRA_NAME from it.
Let me know if that helps.

Categories

Resources