I'm doing bluetooth development for connecting with a PC. I've basicly used the BTChatExample and changed the UUID to the standard PC SPP-profile.
Trying to close a bluetooth application during a blocking read, by closing the BluetoothSocket will leave the Bluetooth stack in a unusable state. This can only be fixed by disabling and enabling bluetooth and restarting the application. Checking logcat, you can see that some of the internal methods are failing, leaving a open port. Any information on this?
First of all there seams to be differences on how bluetooth is implemented on N1 and HTC Legend/Desire both running 2.1, do you know anything about this?
Connecting isn't 100% reliable, sometimes I get a warning saying ~PortSystemContext init: FAILED. This leaves bluetooth unusable, and restarting is needed.
Am I right in assuming that SPP is the only profile supported for use with the APIs? That's what the docs on the BluetoothAdapter says.
I would love to discuss issues on bluetooth with a developer and iron out these bugs so that Android can have good proper Bluetooth support it deserves.
Closing a socket in one thread during a blocking read should definitely cause the read to return (by throwing IOException) and should not leave the stack in a 'bad state'. This is the behavior on Droid and Nexus.
I spoke directly to the original poster, and he had observed this problem on HTC Legend and HTC Desire. It appears like they are not implementing the API's correctly. I am raising the issue with them.
You are correct that SPP/RFCOMM is the only profile that is intended for use with the API's. SPP/RFCOMM gets you a streaming socket which is good enough for a lot of use cases.
For now I recommend BT development on Nexus One / Motorola Droid, which are considered 'reference' implementations of the Bluetooth API's.
May I suggest that you do not perform blocking read() calls unless you have first checked that there is data ready to be read by using inputstream.available() which returns an integer indicating how many bytes are waiting in the input buffer.
long timeouttime = gettimeinseconds() + 2;
String response = "";
while (gettimeinseconds() < timeouttime) {
if (inputstream.available() > 0)
response = response + inputstream.read();
} else {
Thread.sleep(100); // sleep to slow down the while() loop.
}
}
return response;
That's just pseudo code, and its oversimplified. The bottom line is that we're not performing any blocking calls (read()) unless we're sure they will return immediately without delay.
Also, I highly recommend using BufferedInputStream instead of the standard InputStream.
Anyone could solve this problem ?
I try the following code :
// Keep listening to the InputStream while connected
while (!isInterrupted)
{
try
{
//Clear buffer
buffer = new byte[1024];
// Read from the InputStream
if (mmInStream != null && mmInStream.available() > 0)
{
if (isInterrupted)
break;
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(Act_Main.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
}
else
{
try
{
synchronized (this)
{
this.wait(100);
}
if (isInterrupted)
break;
}
catch(InterruptedException ex)
{
Log.e(TAG, "WAIT_EXCEPTION:"+ ex.getMessage());
}
}
}
catch(Exception ex)
{
Log.e(TAG, "disconnected", ex);
connectionLost();
break;
}
}
And I changed the isInterrupted boolean in the cancel() method. Here is my stop() method:
/**
* Stop all threads
*/
public synchronized void stop()
{
isStop = true ;
if (D)
Log.d(TAG, "stop");
if(mConnectThread != null)
{
mConnectThread.cancel();
mConnectThread = null;
}
if(mConnectedThread != null)
{
mConnectedThread.cancel();
mConnectedThread = null;
}
setState(STATE_NONE);
}
Related
We are experimenting with a bunch of new tablets, and every one we tried is having issues with slow transfer rates with the RN4678 board. We currently use the Lenovo M10 FHD Plus. We tried a few such as the Teclast M40S, Nokia T20, and Samsung Galaxy Tab A8. The first two had horrible transfer rates, while the latter was okay but not ideal. We cannot use the Lenovo M10 Plus 3rd Gen because the buttons are too close to the corner to use with our tablet holders.
Here is my code:
public void SendMessage(BluetoothSocket socket, String msg) {
OutputStream outStream;
try {
outStream = BluetoothConnectionService.outputStream;
outStream.write("S".getBytes());
Thread.sleep(4000);
processThread = true;
mApp.running = true;
BluetoothSocketListener bsl = new BluetoothSocketListener(socket,
CollectingDetail.this);
Thread messageListener = new Thread(bsl);
messageListener.start();
timer = new CounterClass(remaingTime, 1000);
timer.start();
bt_stop.setText("Stop");
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
registerReceiver(bluetoothReceiver, filter);
bluetoothReceiver.setRegistered(true);
} catch (IOException | InterruptedException e) {
Log.e("BLUETOOTH_COMMS", e.getMessage());
connectSocket();
}
}
public static class BluetoothSocketListener implements Runnable {
private final WeakReference<CollectingDetail> wrActivity;
private BluetoothSocket socket;
public BluetoothSocketListener(BluetoothSocket socket, CollectingDetail collectingDetail) {
this.socket = socket;
wrActivity = new WeakReference<CollectingDetail>(collectingDetail);
}
#Override
public void run() {
final CollectingDetail activity = wrActivity.get();
if (activity != null) {
activity.inStream = null;
if (!Thread.currentThread().isInterrupted()) {
int bufferSize = 512;
byte[] buffer = new byte[bufferSize];
Log.i("Bluetooth bytes", new String(buffer));
activity.inStream = BluetoothConnectionService.inputStream;
int availableBytes;
int bytesRead = -1;
String message = "";
while (activity.processThread) {
message = "";
try {
availableBytes = activity.inStream.available();
if (availableBytes > 0) {
bytesRead = activity.inStream.read(buffer);
if (bytesRead != -1 && bytesRead < bufferSize) {
message = new String(buffer, 0, bytesRead);
if (activity.mainHandler != null) {
activity.mainHandler.post(new MessagePoster(message, activity));
}
}
}
} catch (IOException e) {
Log.e("BLUETOOTH_COMMS", "Error reading bytes");
try {
socket.close();
} catch (IOException e1) {
Log.e("BLUETOOTH_COMMS", "Could not close socket");
}
activity.processThread = false;
}
}
}
}
}
}
public void seprateData(String message) {
try {
message = message.replaceAll("(\\r\\n|\\n|\\r)", ",");
String[] a = message.split(",");
boolean goodData = false;
for (int i = 0; i < a.length; i++) {
final String data = a[i];
if (data.length() > 0 && !data.equals(" ")) {
if (data.length() >= 10 && data.startsWith("5A")) {
al_sepratedMessageList.add(data);
goodData = true;
}
}
}
if (goodData) {
calculation();
if (ConnectThrough.equalsIgnoreCase("usb")) {
UsbConnectionSerivce.sendMessage("K");
} else {
BluetoothConnectionService.sendMessage(socket, "K");
}
}
} catch (Exception e) {
Log.e("BiSym", "Error Parsing BiSym Data");
}
}
Is there any way we can increase the transfer rate without changing the firmware? It appears others have faced similar problems, but none of the answers have a real solution. Could you please help me out. Thanks.
I fear this may not be software-solvable and may be an issue with BT hardware or firmware. How would I communicate with my boss about this?
I fear this may not be software-solvable and may be an issue with BT hardware or firmware. How would I communicate with my boss about this?
The difference is in the quality of the filtering of the signal, a better filter, narrower bandwidth, means lower Signal to Noise Ratio. Lower SNR means faster transfer.
Better analog filters, mean more components and slightly more cost and loss, however, due to the wide-band nature of Bluetooth, most analog filters can only filter out of band signals (nearby AM/FM/TV broadcasters).
In addition to the analog filters, digital filters are applied to the signal to narrow the bandwidth within the band, this technique incurs little loss, but requires more processing power to be included in the chip, more transistors, more costs.
The order of the filter and the type FIR or IIR determine the characteristics of the filer.
Most designers will minimize the cost to meet the minimum specifications, some will balance the cost versus performance and go further, you never know.
You tell your boss, the the better platforms perform digital filtering well beyond what the Bluetooth specification requires.
I just tested the Teclast M40 Plus which doesn't have this problem.
Something wants to make me believe it is an issue with the UNISOC Bluetooth stack. The Teclast M40 Plus has MediaTek which doesn't have this issue.
EDIT: Also tested on Lenovo M10 Plus 3rd Gen with MediaTek Helio G80, no issue. If we have to use it, we may need a new tablet holder.
Have you repeated the tests? Bluetooth uses the same frequencies as 2.4 GHz Wifi and microwave ovens. In a congested Wifi environment, and/or too many Bluetooth connections(mice, keyboards, headphones, speakers, watches ...), slowdowns are normal for high-speed transfers.
Wifi 6 MIMO utilizes all three distinct channels in the 2.4G band, (1,6,11) of the 12 overlapping. Higher bandwidth/speeds are available in 5.4G (and above), but wall penetration and signal propagation factors keep 2.4G as the primary band in the absence of other possibilities.
There is only so much space in the 2.4G band that Bluetooth operates.
Repeat your tests in an area without interference and see if you get the same results.
A cheap isolation method is to build a Faraday Cage out of .25 inch chicken wire fencing. Isolate the sender and receiver inside of the cage and measure the throughput.
In an uncontrolled environment, hundreds to thousands of tests for each device are required across different time spans to establish a true baseline measurement. You never know when you neighbor is going to start or stop a variable bit-rate video, or move a mouse.Every action contributes to the background noise in the band.
I have an Android tablet that acts as a Bluetooth server and allows up to 6 clients to connect (I set this limit, knowing that the theoretical maximum is 7, as per the Bluetooth piconet specification). I tried with two different tablet models from the same manufacturer: Huawei Mediapad T3 and Huawei MediaPad T5. On the T3, I can get up to 6 connections that remain stable and allow communicating almost seamlessly with the clients. On the T5, however, the tablet will not accept more than 4 simultaneous connections. If I disconnect one device, one of the two remaining ones which keep requesting to connect gets accepted in its place. I do not get any error messages from the calls to the accept() method, or any other errors regarding Bluetooth in the Android Logcat.
Both tablets run under EMUI 8.0.0 (the Huawei brew of Android 8.0.0).
Here is the code I run for the thread that accepts the connections :
private class AcceptThread extends Thread {
private BluetoothServerSocket serverSocket;
AcceptThread(String name, String uuidStr) {
btAdapter.cancelDiscovery();
try{
serverSocket = btAdapter.listenUsingInsecureRfcommWithServiceRecord(name, UUID.fromString(uuidStr));
} catch (IOException ex){
onStatusListener.OnStatus("server.error.COULD_NOT_LISTEN");
Log.e(TAG, "Could not open socket", ex);
}
}
public void run() {
onStatusListener.OnStatus("server.listening");
while (connections.size() < maxConnections) {
setState(STATE_LISTENING);
try {
BluetoothSocket socket = serverSocket.accept();
if (socket != null) {
ConnectedThread conn = new ConnectedThread(socket);
setState(STATE_CONNECTING);
conn.start();
connections.add(conn);
conn.setOnDisconnectEvent(() -> {
onStatusListener.OnStatus("server.disconnected." + socket.getRemoteDevice().getAddress());
connections.remove(conn);
});
onStatusListener.OnStatus("server.connected." + socket.getRemoteDevice().getAddress());
}
} catch (IOException ex) {
onStatusListener.OnStatus("server.error.COULD_NOT_ACCEPT");
Log.e(TAG, "Socket's accept() method failed", ex);
break;
}
}
onStatusListener.OnStatus("server.not_listening");
cancel();
}
void cancel(){
try {
setState(STATE_NONE);
serverSocket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close server socket", e);
}
}
}
What can cause my code to work on the T3 tablet and not on the T5?
Is there anything I could do differently to get my 6 simultaneous connections working with both tablets?
When it comes to the maximum connections allowed by a device, the limitation is usually related to the hardware and not the software. In other words, it is the Bluetooth hardware on the T5 that is preventing you from creating further connections. This is evident by the fact that both tablets are running the same OS yet still exhibit this difference.
What I find strange is the fact that you are not getting any error response back when you attempt to exceed the number of possible connections. Unfortunately I don't think there is a way to increase the limit on the T3. I would contact Huawei if possible as they would be able to confirm this discrepancy.
I am developing an androd bluetooth telnet(?) server which gets commands via bluetooth OPP. My plan is to monitor incoming Opp push, check if it is from certain user, then starting a worker service which actually performs given work.
So I researched information about receiving bluetooth incoming OPP, and I found that killing BluetoothOppService is a key point in this SO thread.
So I wrote the codes below to accept incoming OPP push.
private void KillOriginalService()
{
java.lang.Process suProcess=null;
int pid;
try
{
String[] command = {
"/system/bin/sh",
"-c",
"ps|grep com.android.bl"
};
suProcess = Runtime.getRuntime().exec(command);
DataOutputStream os = new DataOutputStream(suProcess.getOutputStream());
DataInputStream osRes = new DataInputStream(suProcess.getInputStream());
if (null != os && null != osRes)
{
String line;
while (osRes.available() > 0)
{
line = osRes.readLine();
if (line.contains("1002"))
{
String[] words=line.split(" ");
//pid=Integer.parseInt(words[0]);
final String p=words[0];
new ExecuteAsRootBase(){
#Override
protected ArrayList<String> getCommandsToExecute()
{
// TODO: Implement this method
ArrayList<String> list=new ArrayList<String>();
list.add("system/bin/kill -9 " + p);
return list;
}
}.execute();
Log.v(TAG,"Success kill");
return;
}
}
}
}
catch (IOException e)
{
Log.e(TAG, "error occured trying to kill opp service ",e);
}
}
And the following code to get ServerSocket.
private void getServerSocket()
{
boolean fail = true;
while (fail)
{
try
{
serversocket = null;
Log.v(TAG, "trying to get serverSocket");
serversocket = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord("OPP Listener", UUID.fromString("00001105-0000-1000-8000-00805f9b34fb"));
if(serversocket!=null)
fail = false;
}
catch (IOException e)
{
fail = true;
Log.e(TAG, "Failed to get serversocket " , e);
}
if (fail)
{
KillOriginalService();
}
}
//return serversocket;
}
And this code works but sometimes continually ignore the incomming connection until I restart my service manually, causing the original service to accept the connection, rejecting it (because the incoming file's mime type is null). Also the original service acquires full wakelock, consuming my device's battery significantly. Also even when mine accepts the connection, I have to fail about 2 times before mine accepts the connection instead of the original service.
I read the logcat outputs, and found out that the original BtOppService restarts after I kill it printing OnStartCommand logcat.
I solved the battery consuming problem by Administration screenoff api. But the first problem is not solved.
How can I make my service, not the original service, to receive every incoming connections?
(I am currently solving this problem by using watchdog thread that restarts my service automatically.)
P.S. I have a rooted device and the su works properly.
I finally found a way to stop the system services programmatically: Hook the system service!
As I had a rooted device, I could install XPosed Framework and modules. I can create a module that attatches to the target service, and then returning not START_STICKY can prevent it from being restarted.
However, it turned out to be a XY problem. I finally changed my implenentation to not Killing the system service, but Living with it.
I created an file observer to check if the system has received a file. When a file is received, it started a corresponding service.
It creates synergy effect with Auto-Accept, which makes a phone to accept every bluetooth OPP file transfer requests without having to ask the user(No prompts!).
I have some Bluetooth devices which connect to an Android phone, however I'm having trouble detecting disconnections. The bluetooth devices don't send data packets unless they need to, so it's not an option to use a watchdog on packet reception to detect a disconnect. I've read that you can use the ACLDisconnected broadcast, but this event is never firing for me (I've waited minutes). What is a reliable way to detect a disconnection in Android 6?
Here is my AclDisconnect registering code:
_filter = new IntentFilter();
_filter.AddAction(BluetoothDevice.ActionFound);
_filter.AddAction(BluetoothDevice.ActionBondStateChanged);
_filter.AddAction(BluetoothAdapter.ActionDiscoveryStarted);
_filter.AddAction(BluetoothDevice.ActionAclDisconnected);
_filter.AddAction(BluetoothDevice.ActionAclDisconnectRequested);
context.RegisterReceiver(_bluetoothDeviceReceiver, _filter);
And the callback (which doesn't fire on disconnect)
public override void OnReceive(Context context, Intent intent)
{
string action = intent.Action;
if (action == BluetoothDevice.ActionAclDisconnected || action == BluetoothDevice.ActionAclDisconnectRequested)
{
Interlocked.CompareExchange(ref Disconnected, null, null)?.Invoke();
}
}
I don't know about Xamarin, but in plain Android / Java this is typically done by catching the IOException that is thrown by the InputStream when the stream is closed. For example:
// In your listening thread
InputStream inputStream = socket.getInputStream();
while (true) {
try {
int nextByte = inputStream.read();
// Do something with it
} catch (IOException e) {
// Here you know that you are disconnected
}
}
BluetoothDevice.ActionAclDisconnected is indeed unreliable and the only sure way to detect a disconnection is to catch an IOException, like the previous answer states.
So for this you would have to develop a "ping" mechanism where data is sent every X time.
I have an android app which connects to a server using a socket connection which is kept open while the app is active. If the phone gets inactive (lock screen) or the user presses the home button, the application closes the socket connection and reopens it if the app becomes visible again.
This Pattern works fine on most of the android phones we have (about 15 devices), but the Motorola Milestone, Defy, SE Xperia Arc and the LG Optimus One need very long (>10 secs) to detect if a Wifi is available after and connect to it. So to wait for the best network connection I use the following code (before opening the socket to the server):
public static boolean waitNetworkConnection(Context context, int retries) {
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = getNetworkToTest(cm);
if (ni == null || !ni.isConnected()) {
// sleep a short while to allow system to switch connecting networks.
Tools.sleep(1000);
int counter = 0;
while (counter < retries && (ni == null || (ni.isAvailable() &&
!ni.isConnected()))) {
Tools.sleep(500);
ni = getNetworkToTest(cm);
counter++;
}
}
return (cm.getActiveNetworkInfo() != null &&
cm.getActiveNetworkInfo().isConnected());
}
and this method (use by the one above) to get the connection to test, which prefers a Wifi-Connection if one (not necessary connected) is available:
private static NetworkInfo getNetworkToTest(ConnectivityManager cm) {
NetworkInfo[] nis = cm.getAllNetworkInfo();
NetworkInfo ni = cm.getActiveNetworkInfo();
for (int i = 0; i < nis.length; i++) {
if (nis[i].getType() == 1 /* Wifi */ && nis[i].isAvailable()) {
ni = nis[i];
return(ni);
}
}
return(ni);
}
This works fine for most of the devices, but for the mentioned ones this very often fails and this method tells me to use a mobile network connection and the device switches the connection type while I open a socket connection which leads to a SocketException with a very generic error message so I'm unable to determine if the socket connection is caused by this issue or because of some other network error.
Simply doing a retry doesn't fix this either, as this breaks the handling for the other network errors because it then takes very long to detect a socket timeout (because it is checked twice).
Has anyone else ran into this problem (very slowing connect to Wifi) and has a solution for this?
Yes, this is a tricky problem. One option would be to wait for the right network state broadcast using a BroadcastReceiver.
As described here: How to detect when WIFI Connection has been established in Android?
And here: How can I monitor the network connection status in Android?
There is a project called droidfu that has a HTTP wrapper, that gets round the wi-fi / 3g issue.
Here is a snippet from the code for the BetterHttpRequestBase class:
public BetterHttpResponse send() throws ConnectException {
BetterHttpRequestRetryHandler retryHandler = new BetterHttpRequestRetryHandler(maxRetries);
// tell HttpClient to user our own retry handler
httpClient.setHttpRequestRetryHandler(retryHandler);
HttpContext context = new BasicHttpContext();
// Grab a coffee now and lean back, I'm not good at explaining stuff. This code realizes
// a second retry layer on top of HttpClient. Rationale: HttpClient.execute sometimes craps
// out even *before* the HttpRequestRetryHandler set above is called, e.g. on a
// "Network unreachable" SocketException, which can happen when failing over from Wi-Fi to
// 3G or vice versa. Hence, we catch these exceptions, feed it through the same retry
// decision method *again*, and align the execution count along the way.
boolean retry = true;
IOException cause = null;
while (retry) {
try {
return httpClient.execute(request, this, context);
} catch (IOException e) {
cause = e;
retry = retryRequest(retryHandler, cause, context);
} catch (NullPointerException e) {
// there's a bug in HttpClient 4.0.x that on some occasions causes
// DefaultRequestExecutor to throw an NPE, see
// http://code.google.com/p/android/issues/detail?id=5255
cause = new IOException("NPE in HttpClient" + e.getMessage());
retry = retryRequest(retryHandler, cause, context);
} finally {
// if timeout was changed with this request using withTimeout(), reset it
if (oldTimeout != BetterHttp.getSocketTimeout()) {
BetterHttp.setSocketTimeout(oldTimeout);
}
}
}
// no retries left, crap out with exception
ConnectException ex = new ConnectException();
ex.initCause(cause);
throw ex;
}