Can someone please help me figure out why I can only get a null file descriptor to a Bluetooth socket opened via BluetoothServerSocket.accept()?
My goal is streaming video between two devices over bluetooth, by writing video to a file descriptor on one side and reading it from a file descriptor on the other side. My Bluetooth connection is good, I can send raw data back and forth, but I can only get a file descriptor on the client side. On the server side, using the same code, I can only get a null file descriptor. In the debugger I can see a file descriptor on the server side at mySocket.mSocketIS.this$0.fd, but I can't figure out how to get access to it. Can anyone help? This is Android 4.4.2, here's my code:
First the broken code (Server side):
// Listen for an incoming Bluetooth connection
class AcceptThread extends Thread
{
// Thread that accepts incoming bluetooth connections
public AcceptThread()
{
try
{
// Open a listening server socket. This is non-blocking
btServerSocket = BA.listenUsingRfcommWithServiceRecord("ServerApp", videoUUID);
} catch(IOException e){ btServerSocket = null; }
} // AcceptThread()
public void run()
{
BluetoothSocket btSocket = null;
// Listen until exception or we have a socket
while(true)
{
try
{
// Blocking call to accept an incoming connection. To get out of this, call cancel() which closes the socket, causing .accept() to throw an exception
btSocket = btServerSocket.accept();
// If we get here, we're connected!
Field pfdField = btSocket.getClass().getDeclaredField("mPfd");
pfdField.setAccessible(true);
ParcelFileDescriptor pfd = (ParcelFileDescriptor) pfdField.get(btSocket);
// >>> ERROR - pfd is null <<<< I can see a fd at mySocket.mSocketIS.this$0.fd;, but how do I access it?
FileDescriptor myFd = pfd.getFileDescriptor();
// ... blah blah...
Now the working code (Client side):
// Connect to a remote device as the client (we are the client)
class ConnectThread extends Thread
{
// ctor
// remoteUUID - The UUID of the remote device that we want to connect to
public ConnectThread(BluetoothDevice btDevice, UUID remoteUUID)
{
// Get a BT socket to connect with the given BluetoothDevice
try
{
// MY_UUID is the app's UUID string, also used by the server code
btClientSocket = btDevice.createRfcommSocketToServiceRecord(remoteUUID);
}catch(Exception e){ postUIMessage("ConnectThread exception: " + e.toString()); }
} // ConnectThread ctor
public void run()
{
// Cancel discovery because it will slow down the connection
BA.cancelDiscovery();
try
{
// Connect the device through the socket. This will block until it succeeds or throws an exception. To get out, call cancel() below, which will cause .connect() to throw an exception.
btClientSocket.connect();
Field pfdField = btClientSocket.getClass().getDeclaredField("mPfd");
pfdField.setAccessible(true);
ParcelFileDescriptor pfd = (ParcelFileDescriptor) pfdField.get(btClientSocket);
FileDescriptor myFd = pfd.getFileDescriptor(); // Pass this to Recorder.setOutputFile();
// Yay myFd is good!
I found a fix on my side regarding this issue, we are using bluetooth as a server too. I found the file descriptor from the LocalSocket field in BluetoothSocket. My goal was to get teh file and close it.
int mfd = 0;
Field socketField = null;
LocalSocket mSocket = null;
try
{
socketField = btSocket.getClass().getDeclaredField("mSocket");
socketField.setAccessible(true);
mSocket = (LocalSocket)socketField.get(btSocket);
}
catch(Exception e)
{
Log ( "Exception getting mSocket in cleanCloseFix(): " + e.toString());
}
if(mSocket != null)
{
FileDescriptor fileDescriptor =
mSocket.getFileDescriptor();
String in = fileDescriptor.toString();
//regular expression to get filedescriptor index id
Pattern p = Pattern.compile("\\[(.*?)\\]");
Matcher m = p.matcher(in);
while(m.find()) {
Log ( "File Descriptor " + m.group(1));
mfd = Integer.parseInt(m.group(1));
break;
}
//Shutdown the socket properly
mSocket.shutdownInput();
mSocket.shutdownOutput();
mSocket.close();
mSocket = null;
try { socketField.set(btSocket, mSocket); }
catch(Exception e)
{
Log ("Exception setting mSocket = null in cleanCloseFix(): " + e.toString());
}
//Close the file descriptor when we have it from the Local Socket
try {
ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.adoptFd(mfd);
if (parcelFileDescriptor != null) {
parcelFileDescriptor.close();
Log ( "File descriptor close succeed : FD = " + mfd);
}
} catch (Exception ex) {
Log ( "File descriptor close exception " + ex.getMessage());
}
}
Related
I have a client on a PC and a server on a tablet. I know the MAC addresses for both which means I do not do discoveries.
1. On the client if I use
connectString = "btspp://" + MACaddress + ":4;authenticate=false;encrypt=false;master=false";
It connects fine.
If I change the CN number (4) to anything else, it does not work. How is this number determined?
2. Everything works fine if the tablet is a Samsung with Android 5.0.2 When I use a Qunyico tablet with Android 10, it does not work. I get an error: Failed to connect; [10051] A socket operation was attempted to an unreachable network. What is the problem?
Client on PC – code taken from “Bluetooth-java-client-master”
public class IrcBluetoothClient {
private static void openConnection(String MACaddress) throws IOException {
// Tries to open the connection.
String connectString = "btspp://" + MACaddress + ":4;authenticate=false;encrypt=false;master=false";
StreamConnection connection = (StreamConnection) Connector.open(connectString);
if (connection == null) {
System.err.println("Could not open connection to address: " + MACaddress);
System.exit(1);
}
// Initializes the streams.
OutputStream output = connection.openOutputStream();
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader reader = new BufferedReader(isr);
// Starts the listening service for incoming messages.
ExecutorService service = Executors.newSingleThreadExecutor();
service.submit(new IncomingMessagesLoggingRunnable(connection));
// Main loop of the program which is not complete yet
LocalDevice localDevice = LocalDevice.getLocalDevice();
while (true) {
String toSend = reader.readLine();
byte[] toSendBytes = toSend.getBytes(StandardCharsets.US_ASCII);
output.write(toSendBytes);
System.out.println("[" + localDevice.getFriendlyName() + " - " +
localDevice.getBluetoothAddress() + "]: " + toSend);
System.exit(1);
}
Server on tablet – code taken from https://developer.android.com/guide/topics/connectivity/bluetooth
private static final UUID A_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
public BTacceptConnections( BluetoothAdapter mBluetoothAdapter) {
// Use a temporary object that is later assigned to mmServerSocket
// because mmServerSocket is final.
BluetoothServerSocket tmp = null;
try {
// A_UUID is the app's UUID string, also used by the client code.
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, A_UUID);
} catch (IOException e) {
Log.e(TAG, "Socket's listen() method failed", e);
}
mmServerSocket = tmp;
// Closes the connect socket and causes the thread to finish.
public void cancel(){
try {
mmServerSocket.close();
}catch (IOException e){
}
runFlag = 1;
}
//***********************************************************************************************
//
// This thread runs all the time listening for incoming connections.
//
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned.
while (runFlag == 0) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "Socket's accept() method failed", e);
break;
}
if (socket != null) { // If a connection was accepted
// A connection was accepted. Perform work associated with
// the connection in a separate thread.
// manageMyConnectedSocket(socket);
}else{
try {
mmServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
I know the MAC addresses for both which means I do not do discoveries.
Official Linux Bluetooth protocol stack BlueZ uses D-BUS API to establish bluetooth communication. If you check adapter-api, scanning will create device objects that you need to establish a communication which means discovering is not only done to retrieve MAC addresses only.
Your case might be the same, I would suggest doing discovery first.
I am trying to write an app that passes the coordinates of a ball to Arduino via BT. The coordinates are being sent every 4 ms. For this test I send "123" instead of full coordinates. What am I getting now (on Arduino serial monitor) is "123123123123123..." and it refreshes only after I close the application.
What I want to achieve is "123" in every line, that shows immediately after the message is sent.
Android code BT:
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private OutputStream outStream ;
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket
// because mmSocket is final.
BluetoothSocket tmp = null;
mmDevice = device;
try {
// Get a BluetoothSocket to connect with the given BluetoothDevice.
// MY_UUID is the app's UUID string, also used in the server code.
tmp = device.createInsecureRfcommSocketToServiceRecord(uuid);
} catch (IOException e) {
Log.e(TAG, "Socket's create() method failed", e);
}
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it otherwise slows down the connection.
mBluetoothAdapter.cancelDiscovery();
try {
// Connect to the remote device through the socket. This call blocks
// until it succeeds or throws an exception.
mmSocket.connect();
Log.i(TAG, "run: CONNECTED");
} catch (IOException connectException) {
Log.i(TAG, "run: NOT CONNECTED");
}
}
// Closes the client socket and causes the thread to finish.
public void cancel() {
try {
mmSocket.close();
if(outStream != null)
outStream.close();
finish();
} catch (IOException e) {
Log.e(TAG, "Could not close the client socket", e);
}
}
//Sending Message
public void writeData(String data){
String info = data;
try {
outStream = mmSocket.getOutputStream();
outStream.write(info.getBytes());
Log.i(TAG, "writeData: MSG SENT");
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG, "run: CANT SEND MSG");
}
}
public boolean isConnected(){
return mmSocket.isConnected();
}
}
In my main function I call:
if(connectThread.isConnected())
connectThread.writeData("123");
Arduino code:
String incomingByte;
void setup() {
//pinMode(53, OUTPUT);
Serial.begin(9600);
}
void loop() {
// see if there's incoming serial data:
if (Serial.available() > 0) {
// read the oldest byte in the serial buffer:
incomingByte = Serial.readString();
Serial.println(incomingByte);
delay(10);
}
}
There is no concept of messages in serial communication, unless you make it yourself.
Serial.readString() delimits your "messages" with time (1 second by default) and you are sending "messages" 4 ms apart. This obviously concatenates your "messages".
To actually send messages you need to delimit them. You can do that by sending lines.
On Android, you need to end the message with a new line character:
outStream.write(info.getBytes());
outStream.write(10); // send a new line character (ASCII code 10)
And on Arduino, you need to read, until you find a new line character:
incomingByte = Serial.readStringUntil('\n');
Serial.read(); // remove the leftover new line character from the buffer
You need to put at least \n (or maybe \r\n) after the coordinates, or the Bluetooth module just keeps buffering.
I have got list of paired devices of Bluetooth.Now i want to send text to that particular paired device.I have done code for that.
Below is the code:
private void init() throws IOException {
BluetoothAdapter blueAdapter = BluetoothAdapter.getDefaultAdapter();
if (blueAdapter != null) {
if (blueAdapter.isEnabled()) {
Set<BluetoothDevice> bondedDevices = blueAdapter.getBondedDevices();
if(bondedDevices.size() > 0) {
Object[] devices = (Object []) bondedDevices.toArray();
BluetoothDevice device = (BluetoothDevice) devices[position];
ParcelUuid[] uuids = device.getUuids();
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(uuids[0].getUuid());
socket.connect();
outputStream = socket.getOutputStream();
inStream = socket.getInputStream();
}
Log.e("error", "No appropriate paired devices.");
} else {
Log.e("error", "Bluetooth is disabled.");
}
}
}
public void write(String s) throws IOException {
outputStream.write(s.getBytes());
}
public void run() {
final int BUFFER_SIZE = 1024;
byte[] buffer = new byte[BUFFER_SIZE];
int bytes = 0;
int b = BUFFER_SIZE;
while (true) {
try {
bytes = inStream.read(buffer, bytes, BUFFER_SIZE - bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
But i'm getting these Error:
java.io.IOException: read failed, socket might closed or timeout, read ret: -1
04-05 10:53:41.356 5580-5580/? W/System.err: at android.bluetooth.BluetoothSocket.readAll(BluetoothSocket.java:900)
04-05 10:53:41.356 5580-5580/? W/System.err: at android.bluetooth.BluetoothSocket.readInt(BluetoothSocket.java:912)
04-05 10:53:41.356 5580-5580/? W/System.err: at android.bluetooth.BluetoothSocket.connect(BluetoothSocket.java:531)
So, please help me how to solve this error.
What kind of device are you trying to connect to?
-> Based on discussion: If trying to connect to another phone running Android there must be application which is accepting the connection. Working example is available from Google: BluetoothChat
You can try speeding up the connection process:
// Always cancel discovery because it will slow down a connection
mAdapter.cancelDiscovery();
Or you can try using different UUID (are there more supported)?
System.out.println(uuids);
Or you can try using insecure connection:
device.createInsecureRfcommSocketToServiceRecord(...)
BTW: Your code for reading data from inpust stream won't work very well. Take a look at example from Google Chat Application: BluetoothChatService
I am trying to establish Bluetooth communication between Android and IOT (intel galileo) device.
The code at IOT side (i am keeping it as a client), it will send data to android, but here one port number is hard coded. This is in python.
def record_transmit_to_subscriber(self, subscriber, message):
server_addr = subscriber
port = 6
client_socket = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
try:
client_socket.connect((server_addr, port))
client_socket.send(message)
client_socket.close()
return True
except Exception as e:
print "Unable to make connection with subscriber", subscriber
return False
Now at android (server) side:
private static UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
try {
BluetoothServerSocket tmp = null;
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
try {
// MY_UUID is the applications UUID string, also used by the client code
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) {
GlobalUtils.writeLogFile("Error in BLE Listening " + e.getMessage());
}
mmServerSocket = tmp;
} catch (Exception e){
GlobalUtils.writeLogFile("Exception in Accept Thread " + e.getMessage());
}
I do believe there in some problem in this code, at client side it is using port number while at server side it is using uuid. can someone please rectify how to modify this code to make connection work.
Seems You forgot to create socket and streams:
try {
BluetoothDevice bluetoothDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("<MAC_address_of_your_device>");
mSocket = bluetoothDevice.createInsecureRfcommSocketToServiceRecord(MY_UUID);
mSocket.connect();
} catch (IOException e) {
Log.e(TAG, "Fail connect");
}
// Get the input and output streams for BT socket
try {
inStream = mSocket.getInputStream();
outStream = mSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "Fail to open socket streams");
}
Than You can create Thread for read data from inStream (for example inStream.read(<data_buffer>); and Thread for write data to outStream (for example outStream.write(<data_buffer>); outStream.flush();).
I'm trying to find a solution for this setup:
I have a single Android device, which I would like to connect to multiple serial embedded devices...
And here is the thing, using the "Normal" way to retrieve the Bluetooth socket, doesn't work on all devices, and while it does, I can connect to multiple devices, and send and receive data to and from multiple devices.
public final synchronized void connect()
throws ConnectionException {
if (socket != null)
throw new IllegalStateException("Error socket is not null!!");
connecting = true;
lastException = null;
lastPacket = null;
lastHeartBeatReceivedAt = 0;
log.setLength(0);
try {
socket = fetchBT_Socket_Normal();
connectToSocket(socket);
listenForIncomingSPP_Packets();
connecting = false;
return;
} catch (Exception e) {
socket = null;
logError(e);
}
try {
socket = fetchBT_Socket_Workaround();
connectToSocket(socket);
listenForIncomingSPP_Packets();
connecting = false;
return;
} catch (Exception e) {
socket = null;
logError(e);
}
connecting = false;
if (socket == null)
throw new ConnectionException("Error creating RFcomm socket for" + this);
}
private BluetoothSocket fetchBT_Socket_Normal()
throws Exception {
/* The getType() is a hex 0xXXXX value agreed between peers --- this is the key (in my case) to multiple connections in the "Normal" way */
String uuid = getType() + "1101-0000-1000-8000-00805F9B34FB";
try {
logDebug("Fetching BT RFcomm Socket standard for UUID: " + uuid + "...");
socket = btDevice.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
return socket;
} catch (Exception e) {
logError(e);
throw e;
}
}
private BluetoothSocket fetchBT_Socket_Workaround()
throws Exception {
Method m;
int connectionIndex = 1;
try {
logDebug("Fetching BT RFcomm Socket workaround index " + connectionIndex + "...");
m = btDevice.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
socket = (BluetoothSocket) m.invoke(btDevice, connectionIndex);
return socket;
} catch (Exception e1) {
logError(e1);
throw e1;
}
}
private void connectToSocket(BluetoothSocket socket)
throws ConnectionException {
try {
socket.connect();
} catch (IOException e) {
try {
socket.close();
} catch (IOException e1) {
logError("Error while closing socket", e1);
} finally {
socket = null;
}
throw new ConnectionException("Error connecting to socket with" + this, e);
}
}
And here is the thing, while on phones which the "Normal" way doesn't work, the "Workaround" way provides a solution for a single connection. I've searched far and wide, but came up with zip.
The problem with the workaround is mentioned in the last link, both connection uses the same port, which in my case, causes a block, where both of the embedded devices can actually send data, that is not been processed on the Android, while both embedded devices can receive data sent from the Android.
Did anyone handle this before?
There is a bit more reference here,
UPDATE:
Following this (that I posted earlier) I wanted to give the mPort a chance, and perhaps to see other port indices, and how other devices manage them, and I found out the the fields in the BluetoothSocket object are different while it is the same class FQN in both cases:
Detils from an HTC Vivid 2.3.4, uses the "workaround" Technic:
The Socket class type is: [android.bluetooth.BluetoothSocket]
mSocket BluetoothSocket (id=830008629928)
EADDRINUSE 98
EBADFD 77
MAX_RFCOMM_CHANNEL 30
TAG "BluetoothSocket" (id=830002722432)
TYPE_L2CAP 3
TYPE_RFCOMM 1
TYPE_SCO 2
mAddress "64:9C:8E:DC:56:9A" (id=830008516328)
mAuth true
mClosed false
mClosing AtomicBoolean (id=830007851600)
mDevice BluetoothDevice (id=830007854256)
mEncrypt true
mInputStream BluetoothInputStream (id=830008688856)
mLock ReentrantReadWriteLock (id=830008629992)
mOutputStream BluetoothOutputStream (id=830008430536)
**mPort 1**
mSdp null
mSocketData 3923880
mType 1
Detils from an LG-P925 2.2.2, uses the "normal" Technic:
The Socket class type is: [android.bluetooth.BluetoothSocket]
mSocket BluetoothSocket (id=830105532880)
EADDRINUSE 98
EBADFD 77
MAX_RFCOMM_CHANNEL 30
TAG "BluetoothSocket" (id=830002668088)
TYPE_L2CAP 3
TYPE_RFCOMM 1
TYPE_SCO 2
mAccepted false
mAddress "64:9C:8E:B9:3F:77" (id=830105544600)
mAuth true
mClosed false
mConnected ConditionVariable (id=830105533144)
mDevice BluetoothDevice (id=830105349488)
mEncrypt true
mInputStream BluetoothInputStream (id=830105532952)
mLock ReentrantReadWriteLock (id=830105532984)
mOutputStream BluetoothOutputStream (id=830105532968)
mPortName "" (id=830002606256)
mSocketData 0
mSppPort BluetoothSppPort (id=830105533160)
mType 1
mUuid ParcelUuid (id=830105714176)
Anyone have some insight...
WOW, every time this strike me down with one big WTF?
This was a race condition issue, which clearly works on one version of android, and not on another. On Android peer I was parsing the packets received from the socket:
public class SocketListener
implements Runnable {
private boolean stop;
private OnIncomingPacketListener packetListener;
#Override
public void run() {
InputStream inputStream;
try {
stop = false;
inputStream = socket.getInputStream();
while (!stop) {
Packet packet = Packet.getPacket(inputStream);
lastPacket = packet;
if (packet.getDescriptor() == Packet.HeartBeat)
lastHeartBeatReceivedAt = System.currentTimeMillis();
else if (packet.getDescriptor() == Packet.LogEntry)
log.append(((LogEntryPacket) packet).getLogEntry());
synchronized (this) {
if (packetListener != null)
packetListener.onIncomingData(EmbeddedDevice.this, packet);
}
}
} catch (IOException e) {
logError("----- BLUETOOTH IO ERROR -----\n #: " + EmbeddedDevice.this, e);
return;
} catch (RuntimeException e) {
logError("----- BLUETOOTH LISTENER ERROR -----\n #: " + EmbeddedDevice.this, e);
throw e;
} finally {
socketListeningThread = null;
}
}
}
Where the Packet.getPacket(inputStream) is:
public static synchronized Packet getPacketInstance(InputStream inputStream)
throws IOException {
int data = inputStream.read();
Packet type = null;
for (Packet packetType : values())
if (packetType.packetType == data) {
type = packetType;
break;
} // race condition here...
if (type == null)
throw new IllegalArgumentException("Unknown packet type: " + data);
try {
Packet packet = type.incomingPacketType.newInstance();
packet.setDescriptor(type);
packet.readPacketData(inputStream);
return packet;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException("Error instantiating type: " + type.incomingPacketType.getName(), e);
}
}
And every time a packet is completed, the next thread should have gone in to perform it parsing.
My guess is that there is some sort of lock on the port, that together with my implementation caused the second thread to block indefinitely, once I've removed the parsing to different instances per thread, the issue dissolved.
This insight was inspired by Daniel Knoppel, the guy from the mPort link.
Thanks Daniel!