I am new to android. I am designing an android application that receives serial data from a hardware device through bluetooth. I am working on Htc desire S. I used the sample Bluetooth chat code to receive data. But the data received is incorrect. It misses some values. Can anyone please provide me any other sample code to receive large amount of data through bluetooth and save it in a file.
try this code :
Activity:
package Android.Arduino.Bluetooth;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.TextView;
import android.widget.EditText;
import android.widget.Button;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;
import java.util.UUID;
public class MainActivity extends Activity
{
TextView myLabel;
EditText myTextbox;
BluetoothAdapter mBluetoothAdapter;
BluetoothSocket mmSocket;
BluetoothDevice mmDevice;
OutputStream mmOutputStream;
InputStream mmInputStream;
Thread workerThread;
byte[] readBuffer;
int readBufferPosition;
int counter;
volatile boolean stopWorker;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button openButton = (Button)findViewById(R.id.open);
Button sendButton = (Button)findViewById(R.id.send);
Button closeButton = (Button)findViewById(R.id.close);
myLabel = (TextView)findViewById(R.id.label);
myTextbox = (EditText)findViewById(R.id.entry);
//Open Button
openButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
try
{
findBT();
openBT();
}
catch (IOException ex) { }
}
});
//Send Button
sendButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
try
{
sendData();
}
catch (IOException ex) { }
}
});
//Close button
closeButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
try
{
closeBT();
}
catch (IOException ex) { }
}
});
}
void findBT()
{
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null)
{
myLabel.setText("No bluetooth adapter available");
}
if(!mBluetoothAdapter.isEnabled())
{
Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBluetooth, 0);
}
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if(pairedDevices.size() > 0)
{
for(BluetoothDevice device : pairedDevices)
{
if(device.getName().equals("MattsBlueTooth"))
{
mmDevice = device;
break;
}
}
}
myLabel.setText("Bluetooth Device Found");
}
void openBT() throws IOException
{
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //Standard SerialPortService ID
mmSocket = mmDevice.createRfcommSocketToServiceRecord(uuid);
mmSocket.connect();
mmOutputStream = mmSocket.getOutputStream();
mmInputStream = mmSocket.getInputStream();
beginListenForData();
myLabel.setText("Bluetooth Opened");
}
void beginListenForData()
{
final Handler handler = new Handler();
final byte delimiter = 10; //This is the ASCII code for a newline character
stopWorker = false;
readBufferPosition = 0;
readBuffer = new byte[1024];
workerThread = new Thread(new Runnable()
{
public void run()
{
while(!Thread.currentThread().isInterrupted() && !stopWorker)
{
try
{
int bytesAvailable = mmInputStream.available();
if(bytesAvailable > 0)
{
byte[] packetBytes = new byte[bytesAvailable];
mmInputStream.read(packetBytes);
for(int i=0;i<bytesAvailable;i++)
{
byte b = packetBytes[i];
if(b == delimiter)
{
byte[] encodedBytes = new byte[readBufferPosition];
System.arraycopy(readBuffer, 0, encodedBytes, 0, encodedBytes.length);
final String data = new String(encodedBytes, "US-ASCII");
readBufferPosition = 0;
handler.post(new Runnable()
{
public void run()
{
myLabel.setText(data);
}
});
}
else
{
readBuffer[readBufferPosition++] = b;
}
}
}
}
catch (IOException ex)
{
stopWorker = true;
}
}
}
});
workerThread.start();
}
void sendData() throws IOException
{
String msg = myTextbox.getText().toString();
msg += "\n";
mmOutputStream.write(msg.getBytes());
myLabel.setText("Data Sent");
}
void closeBT() throws IOException
{
stopWorker = true;
mmOutputStream.close();
mmInputStream.close();
mmSocket.close();
myLabel.setText("Bluetooth Closed");
}
}
AND Here the layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:ignore="TextFields,HardcodedText" >
<TextView
android:id="#+id/label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Type here:" />
<EditText
android:id="#+id/entry"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#id/label"
android:background="#android:drawable/editbox_background" />
<Button
android:id="#+id/open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="#id/entry"
android:layout_marginLeft="10dip"
android:text="Open" />
<Button
android:id="#+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#id/open"
android:layout_toLeftOf="#id/open"
android:text="Send" />
<Button
android:id="#+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#id/send"
android:layout_toLeftOf="#id/send"
android:text="Close" />
</RelativeLayout>
Here for Manifest:
add to Application
// permission must be enabled complete
<manifest ....>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<application>
</application>
</manifest>
I tried this out for transmitting continuous data (float values converted to string) from my PC (MATLAB) to my phone. But, still my App misreads the delimiter '\n' and still data gets garbled. So, I took the character 'N' as the delimiter rather than '\n' (it could be any character that doesn't occur as part of your data) and I've achieved better transmission speed - I gave just 0.1 seconds delay between transmitting successive samples - with more than 99% data integrity at the receiver i.e. out of 2000 samples (float values) that I transmitted, only 10 were not decoded properly in my application.
My answer in short is: Choose a delimiter other than '\r' or '\n' as these create more problems for real-time data transmission when compared to other characters like the one I've used. If we work more, may be we can increase the transmission rate even more. I hope my answer helps someone!
The issue with the null connection is related to the findBT() function. you must change the device name from "MattsBlueTooth" to your device name as well as confirm the UUID for your service/device. Use something like BLEScanner app to confrim both on Android.
Take a look at incredible Bluetooth Serial class that has onResume() ability that helped me so much.
I hope this helps ;)
You can use android-bluetooth-serial library to send/receive messages.
Add this package as dependency in app build.gradle.
dependencies {
implementation 'com.github.harry1453:android-bluetooth-serial:v1.1'
// RxJava is also required.
implementation 'io.reactivex.rxjava2:rxjava:2.1.12'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
}
In your app, add a class to interact with device.
private void connectDevice(String mac) {
bluetoothManager.openSerialDevice(mac)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onConnected, this::onError);
}
private void onConnected(BluetoothSerialDevice connectedDevice) {
// You are now connected to this device!
// Here you may want to retain an instance to your device:
deviceInterface = connectedDevice.toSimpleDeviceInterface();
// Listen to bluetooth events
deviceInterface.setListeners(this::onMessageReceived, this::onMessageSent, this::onError);
// Let's send a message:
deviceInterface.sendMessage("Hello world!");
}
private void onMessageSent(String message) {
// We sent a message! Handle it here.
Toast.makeText(context, "Sent a message! Message was: " + message, Toast.LENGTH_LONG).show(); // Replace context with your context instance.
}
private void onMessageReceived(String message) {
// We received a message! Handle it here.
Toast.makeText(context, "Received a message! Message was: " + message, Toast.LENGTH_LONG).show(); // Replace context with your context instance.
}
You can see the readme page for more details on using this package.
Related
I'm looking for a way to send data between my Android Device and Google Glass that doesn't rely on Cloud API's. Is this supported? I see the Bluetooth connections in the My Glass app, which makes me think it can be done. Is there an example source code that shows how this is done? Or do I have to decompile the MyGlass app to figure it out?
Is there a preferred method for doing this kind of data transfer? Ideally I'd like to transfer data in both directions.
Ok, for the requesters....
EDIT: The code below still works, but I've put it into a git repo for those who are interested...
https://github.com/NathanielWaggoner/GoogleGlassBlutooth
Here is my Bluetooth Host/Client code. It's not perfect - You're going to need some patience, and there are some bugs on reconnection and such, but it does work. I've been sending data up to Glass From the Hand Held and driving UI updates (publishing live cards, updating live cards etc...) for about three days using this now.
Host:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;
public class BluetoothHost extends Activity {
public static String msgToSend="";
public static final int STATE_CONNECTION_STARTED = 0;
public static final int STATE_CONNECTION_LOST = 1;
public static final int READY_TO_CONN = 2;
public static final String DEVICE_NAME = "device_name";
public static final String TOAST = "toast";
// our last connection
ConnectedThread mConnectedThread;// = new ConnectedThread(socket);
// track our connections
ArrayList<ConnectedThread> mConnThreads;
// bt adapter for all your bt needs (where we get all our bluetooth powers)
BluetoothAdapter myBt;
// list of sockets we have running (for multiple connections)
ArrayList<BluetoothSocket> mSockets = new ArrayList<BluetoothSocket>();
// list of addresses for devices we've connected to
ArrayList<String> mDeviceAddresses = new ArrayList<String>();
// just a name, nothing more...
String NAME="G6BITCHES";
// We can handle up to 7 connections... or something...
UUID[] uuids = new UUID[2];
// some uuid's we like to use..
String uuid1 = "05f2934c-1e81-4554-bb08-44aa761afbfb";
String uuid2 = "c2911cd0-5c3c-11e3-949a-0800200c9a66";
// just a tag..
String TAG = "G6 Bluetooth Host Activity";
// constant we define and pass to startActForResult (must be >0), that the system passes back to you in your onActivityResult()
// implementation as the requestCode parameter.
int REQUEST_ENABLE_BT = 1;
AcceptThread accThread;
TextView connectedDevices;
Handler handle;
BroadcastReceiver receiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// the activity for this is pretty stripped, just a basic selection ui....
setContentView(R.layout.activity_main);
uuids[0] = UUID.fromString(uuid1);
uuids[1] = UUID.fromString(uuid2);
connectedDevices = (TextView) findViewById(R.id.connected_devices_values);
handle = new Handler(Looper.getMainLooper()) {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case STATE_CONNECTION_STARTED:
connectedDevices.setText(msg.getData().getString("NAMES"));
break;
case STATE_CONNECTION_LOST:
connectedDevices.setText("");
startListening();
break;
case READY_TO_CONN:
startListening();
default:
break;
}
}
};
// ....
myBt = BluetoothAdapter.getDefaultAdapter();
// run the "go get em" thread..
accThread = new AcceptThread();
accThread.start();
}
public void startListening() {
if(accThread!=null) {
accThread.cancel();
}else if (mConnectedThread!= null) {
mConnectedThread.cancel();
} else {
accThread = new AcceptThread();
accThread.start();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private class AcceptThread extends Thread {
private BluetoothServerSocket mmServerSocket;
BluetoothServerSocket tmp;
public AcceptThread() {
BluetoothServerSocket tmp = null;
try {
tmp = myBt.listenUsingInsecureRfcommWithServiceRecord(NAME, uuids[0]);
} catch (IOException e) { }
mmServerSocket = tmp;
}
public void run() {
Log.e(TAG,"Running?");
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
e.printStackTrace();
break;
}
// If a connection was accepted
if (socket != null) {
try {
mmServerSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
break;
}
}
}
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
Message msg = handle.obtainMessage(READY_TO_CONN);
handle.sendMessage(msg);
} catch (IOException e) { }
}
}
private void manageConnectedSocket(BluetoothSocket socket) {
// start our connection thread
mConnectedThread = new ConnectedThread(socket);
mConnectedThread.start();
// Send the name of the connected device back to the UI Activity
// so the HH can show you it's working and stuff...
String devs="";
for(BluetoothSocket sock: mSockets) {
devs+=sock.getRemoteDevice().getName()+"\n";
}
// pass it to the UI....
Message msg = handle.obtainMessage(STATE_CONNECTION_STARTED);
Bundle bundle = new Bundle();
bundle.putString("NAMES", devs);
msg.setData(bundle);
handle.sendMessage(msg);
}
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
Log.d(TAG, "create ConnectedThread");
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the BluetoothSocket input and output streams
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "temp sockets not created", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[1024];
int bytes;
// Keep listening to the InputStream while connected
while (true) {
try {
//byte[] blah = ("System Time:" +System.currentTimeMillis()).getBytes();
if(!msgToSend.equals("")) {
Log.e(TAG,"writing!");
write(msgToSend.getBytes());
setMsg("");
}
Thread.sleep(1000);
} catch (Exception e) {
Log.e(TAG, "disconnected", e);
connectionLost();
}
}
}
public void connectionLost() {
Message msg = handle.obtainMessage(STATE_CONNECTION_LOST);
handle.sendMessage(msg);
}
/**
* Write to the connected OutStream.
* #param buffer The bytes to write
*/
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
} catch (IOException e) {
Log.e(TAG, "Exception during write", e);
connectionLost();
}
}
public void cancel() {
try {
mmSocket.close();
Message msg = handle.obtainMessage(READY_TO_CONN);
handle.sendMessage(msg);
} catch (IOException e) {
Log.e(TAG, "close() of connect socket failed", e);
}
}
}
public static synchronized void setMsg(String newMsg) {
msgToSend = newMsg;
}
public static class HostBroadRec extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Bundle b= intent.getExtras();
String vals ="";
for(String key: b.keySet()) {
vals+=key+"&"+b.getString(key)+"Z";
}
BluetoothHost.setMsg(vals);
}
}
}
Client:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Set;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
public class BluetoothClient extends Activity {
public static final int READY_TO_CONN =0;
public static final int CANCEL_CONN =1;
public static final int MESSAGE_READ =2;
// holds the bluetooth names/ids that we're associated with.
ArrayAdapter<String> btArray;
// bt adapter for all your bt needs
BluetoothAdapter myBt;
String NAME="G6BITCHES";
String TAG = "G6 Bluetooth Slave Activity";
UUID[] uuids = new UUID[2];
// some uuid's we like to use..
String uuid1 = "05f2934c-1e81-4554-bb08-44aa761afbfb";
String uuid2 = "c2911cd0-5c3c-11e3-949a-0800200c9a66";
// DateFormat df = new DateFormat("ddyyyy")
ConnectThread mConnThread;
Spinner devices;
Handler handle;
// constant we define and pass to startActForResult (must be >0), that the system passes back to you in your onActivityResult()
// implementation as the requestCode parameter.
int REQUEST_ENABLE_BT = 1;
// bc for discovery mode for BT...
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 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);
// Add the name and address to an array adapter to show in a ListView
if(device!= null) {
if(device.getName().contains("Nexus")) {
} else {
btArray.add(device.getName() + "\n" + device.getAddress());
}
}
update();
}
}
};
Context ctx;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// publishCards(this);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
ctx = this;
handle = new Handler(Looper.getMainLooper()) {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case READY_TO_CONN:
mConnThread=null;
update();
break;
case CANCEL_CONN:
break;
case MESSAGE_READ:
byte[] readBuf = (byte[]) msg.obj;
// construct a string from the valid bytes in the buffer
String readMessage = new String(readBuf, 0, msg.arg1);
Log.e(TAG,"received: "+readMessage);
if (readMessage.length() > 0) {
// do soemthing...
}
// updateCards(ctx, readMessage);
// update()
// mConversationArrayAdapter.add(mConnectedDeviceName+": " + readMessage);
break;
default:
break;
}
}
};
btArray = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, android.R.id.text1);
btArray.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
uuids[0] = UUID.fromString(uuid1);
uuids[1] = UUID.fromString(uuid2);
// spinner for displaying available devices for pairing
devices = (Spinner) findViewById(R.id.devices_spinner);
devices.setAdapter(btArray);
// use the same UUID across an installation
// should allow clients to find us repeatedly
myBt = BluetoothAdapter.getDefaultAdapter();
if (myBt == null) {
Toast.makeText(this, "Device Does not Support Bluetooth", Toast.LENGTH_LONG).show();
}
else if (!myBt.isEnabled()) {
// we need to wait until bt is enabled before set up, so that's done either in the following else, or
// in the onActivityResult for our code ...
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
} else {
detectAndSetUp();
}
setContentView(R.layout.bluetooth_activity_layout);
}
#Override
public void onDestroy() {
unregisterReceiver(mReceiver);
super.onDestroy();
}
#Override
protected void onActivityResult (int requestCode, int resultCode, Intent data){
if(requestCode == REQUEST_ENABLE_BT) {
if (resultCode != RESULT_OK) {
Toast.makeText(this, "Failed to enable Bluetooth", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Bluetooth Enabled", Toast.LENGTH_LONG).show();
detectAndSetUp();
}
}
}
private void detectAndSetUp() {
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
Set<BluetoothDevice> pairedDevices = myBt.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
if(device.getName().contains("Nexus")) {
} else {
btArray.add(device.getName() + "\n" + device.getAddress());
}
// Add the name and address to an array adapter to show in a ListView
// btArray.add(device.getName() + "\n" + device.getAddress());
// update();
}
}
myBt.startDiscovery();
}
public void update() {
devices = (Spinner) findViewById(R.id.devices_spinner);
devices.setAdapter(btArray);
devices.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int position, long id) {
if(mConnThread!=null) {
Log.e(TAG,"Canceling old connection, and starting new one.");
mConnThread.cancel();
} else {
Log.e(TAG,"got a thing...");
String str = ((TextView)arg1).getText().toString();
Log.e(TAG,"tots: "+str);
String[] vals = str.split("\n");
Log.e(TAG,"mac: "+vals[1]);
BluetoothDevice dev = myBt.getRemoteDevice(vals[1]);
mConnThread = new ConnectThread(dev);
mConnThread.run();
}
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.bluetooth, menu);
return true;
}
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
Log.e(TAG,"ConnectThread start....");
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// this seems to work on the note3...
// you can remove the Insecure if you want to...
tmp = device.createInsecureRfcommSocketToServiceRecord(uuids[0]);
// Method m;
// this is an approach I've seen others use, it wasn't nescesary for me,
// but your results may vary...
// m = device.getClass().getMethod("createInsecureRfcommSocket", new Class[] {int.class});
// tmp = (BluetoothSocket) m.invoke(device, 1);
// } catch (NoSuchMethodException e1) {
// // TODO Auto-generated catch block
// e1.printStackTrace();
// } catch (IllegalArgumentException e2) {
// // TODO Auto-generated catch block
// e2.printStackTrace();
// } catch (IllegalAccessException e3) {
// // TODO Auto-generated catch block
// e3.printStackTrace();
// } catch (InvocationTargetException e4) {
// // TODO Auto-generated catch block
// e4.printStackTrace();
// }
// if(tmp.isConnected()) {
// break
// }
} catch (Exception e) {
Log.e(TAG,"Danger Will Robinson");
e.printStackTrace();
}
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it will slow down the connection
myBt.cancelDiscovery();
Log.e(TAG,"stopping discovery");
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
Log.e(TAG,"connecting!");
mmSocket.connect();
} catch (IOException connectException) {
Log.e(TAG,"failed to connect");
// Unable to connect; close the socket and get out
try {
Log.e(TAG,"close-ah-da-socket");
mmSocket.close();
} catch (IOException closeException) {
Log.e(TAG,"failed to close hte socket");
}
Log.e(TAG,"returning..");
return;
}
Log.e(TAG,"we can now manage our connection!");
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
mmSocket.close();
Message msg = handle.obtainMessage(READY_TO_CONN);
handle.sendMessage(msg);
} catch (IOException e) { }
}
}
public void manageConnectedSocket(BluetoothSocket mmSocket) {
ConnectedThread t = new ConnectedThread(mmSocket);
t.start();
// manage your socket... I'll probably do a lot of the boiler plate here later
}
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
Log.d(TAG, "create ConnectedThread");
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the BluetoothSocket input and output streams
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "temp sockets not created", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[1024];
int bytes;
// Keep listening to the InputStream while connected
while (true) {
try {
// byte[] blah = ("System Time:" +System.currentTimeMillis()).getBytes();
// write(blah);
// Thread.sleep(1000);
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI Activity
handle.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
// .sendToTarget();
} catch (Exception e) {
Log.e(TAG, "disconnected", e);
connectionLost();
// break;
}
}
}
public void connectionLost() {
Message msg = handle.obtainMessage(CANCEL_CONN);
// Bundle bundle = new Bundle();
// bundle.putString("NAMES", devs);
// msg.setData(bundle);
handle.sendMessage(msg);
}
/**
* Write to the connected OutStream.
* #param buffer The bytes to write
*/
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
// Share the sent message back to the UI Activity
// mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)
// .sendToTarget();
} catch (IOException e) {
Log.e(TAG, "Exception during write", e);
}
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of connect socket failed", e);
}
}
}
}
Host Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="transapps.android_bluetooth_host"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="7"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:name="transapps.android_bluetooth_host.BluetoothHost"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".BluetoothHost$HostBroadRec" >
<intent-filter>
<action android:name="transapps.g6.new.alert" />
</intent-filter>
</receiver>
</application>
</manifest>
Client Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="transapps.android_blutooth"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:name="transapps.android_blutooth.BluetoothClient"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
I'll leave the UI as an exercise to the reader.
There is no preferred method, but if you're looking to do it wirelessly, a Bluetooth 3.0 RFCOMM does work.
If you provide more specifics about the problem you're trying to solve in your question, I'll be able to provide a more specific answer.
Several things I noticed when following this solution (awesome work by the way!)
1) I could only create a bluetooth connection when my phone and the Google Glass were not already paired through the MyGlass app - If you are having trouble establishing a connection, try forgetting that pairing.
2) The Glass API does not support controlling your bluetooth connection through intents using commands like
Intent discoveryIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoveryIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, DISCOVER_DURATION);
startActivityForResult(discoveryIntent, REQUEST_BLU);
In order to make the Glass headset discoverable (rather than the phone it is connecting to), I needed to follow solutions similar to those like user4934624 provided at this question and shantanu gave at this question. I invoked a hidden method to access bluetooth functionality directly. Warning: the hidden method seems to have been there for years, but there is no guarantee it will continue to be in future APIs.
// this method allows us to make the device discoverable without alerting the users
// NOTE!!!! This uses a hidden method, so it may be removed from the API in the future
public void makeDiscoverable (){
Class <?> baClass = BluetoothAdapter.class;
Method[] methods = baClass.getDeclaredMethods();
// we want to use method setScanMode(int mode, int duration)
// there are 2 setScanModes
// so select the first setScanMode you see
// test to see which method is the one we want
//for (int i = 0; i<50; i++) {Log.d(Integer.toString(i), methods[i].getName());}
//I had trouble calling the first setScanMode, so I called the second.
// I need to pass in a discoverable time, but it stays discoverable indefinitely
// Thus you must turn off the setScanMode as soon as the connection is established
// I should probably write in some other security stuff to turn it off if connection fails
// maybe a timer running on a different thread?
Method mSetScanMode = methods[38];
try {
mSetScanMode.invoke(myBt, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,300);
//mSetScanMode.invoke(myBt, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
} catch (Exception e) {
Log.e("discoverable", e.getMessage());
}
}
// this method allows us to make the device not discoverable without alerting the user
// NOTE!!!! This uses a hidden method, so it may be removed from the API in the future
public void makeNotDiscoverable (){
// see notes for makeDiscoverable
Class <?> baClass = BluetoothAdapter.class;
Method [] methods = baClass.getDeclaredMethods();
Method mSetScanMode = methods[38];
try {
mSetScanMode.invoke(myBt, BluetoothAdapter.SCAN_MODE_CONNECTABLE,300);
} catch (Exception e) {
Log.e("discoverable", e.getMessage());
}
}
Note that I first ran a test that output the names of all the methods in the class I mirrored; that allowed me to narrow my search for which method I wanted to use.
The JoeGlass app for android claims to be a replacement for MyGlass. It will talk to your glass directly, using a bluetooth connection. I have not tried yet it but it is open source (github), so if it works, you are good.
My book Beginning Google Glass Development has a full chapter on this topic, with fully working example code on using Bluetooth and socket to transfer data between Glass and Android (or iOS) devices.
The book is available at amazon: http://www.amazon.com/Beginning-Google-Glass-Development-Jeff/dp/1430267887
and the source code is available for download at http://www.apress.com/downloadable/download/sample/sample_id/1562/
I can get my Android app to connect via Bluetooth to my Arduino. However no data can be transmitted between them. Below is my setup and code:
HTC Android v2.2, Bluetooth mate gold modem, Arduino Mega (ATmega1280)
Android Java code:
package com.example.BluetoothExample;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.TextView;
import android.widget.EditText;
import android.widget.Button;
import android.widget.Toast;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;
import java.util.UUID;
public class BluetoothExampleActivity extends Activity {
TextView myLabel;
EditText myTextbox;
BluetoothAdapter mBluetoothAdapter;
BluetoothSocket mmSocket;
BluetoothDevice mmDevice;
OutputStream mmOutputStream;
InputStream mmInputStream;
Thread workerThread;
byte[] readBuffer;
int readBufferPosition;
int counter;
volatile boolean stopWorker;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button openButton = (Button)findViewById(R.id.open);
Button sendButton = (Button)findViewById(R.id.send);
Button closeButton = (Button)findViewById(R.id.close);
myLabel = (TextView)findViewById(R.id.label);
myTextbox = (EditText)findViewById(R.id.entry);
//Open Button
openButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
findBT();
openBT();
}
catch (IOException ex) { }
}
});
//Send Button
sendButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
sendData();
}
catch (IOException ex) {
showMessage("SEND FAILED");
}
}
});
//Close button
closeButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
closeBT();
}
catch (IOException ex) { }
}
});
}
void findBT() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null) {
myLabel.setText("No bluetooth adapter available");
}
if(!mBluetoothAdapter.isEnabled()) {
Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBluetooth, 0);
}
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if(pairedDevices.size() > 0) {
for(BluetoothDevice device : pairedDevices) {
if(device.getName().equals("FireFly-108B")) {
mmDevice = device;
break;
}
}
}
myLabel.setText("Bluetooth Device Found");
}
void openBT() throws IOException {
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); //Standard //SerialPortService ID
mmSocket = mmDevice.createRfcommSocketToServiceRecord(uuid);
mmSocket.connect();
mmOutputStream = mmSocket.getOutputStream();
mmInputStream = mmSocket.getInputStream();
beginListenForData();
myLabel.setText("Bluetooth Opened");
}
void beginListenForData() {
final Handler handler = new Handler();
final byte delimiter = 10; //This is the ASCII code for a newline character
stopWorker = false;
readBufferPosition = 0;
readBuffer = new byte[1024];
workerThread = new Thread(new Runnable() {
public void run() {
while(!Thread.currentThread().isInterrupted() && !stopWorker) {
try {
int bytesAvailable = mmInputStream.available();
if(bytesAvailable > 0) {
byte[] packetBytes = new byte[bytesAvailable];
mmInputStream.read(packetBytes);
for(int i=0;i<bytesAvailable;i++) {
byte b = packetBytes[i];
if(b == delimiter) {
byte[] encodedBytes = new byte[readBufferPosition];
System.arraycopy(readBuffer, 0, encodedBytes, 0, encodedBytes.length);
final String data = new String(encodedBytes, "US-ASCII");
readBufferPosition = 0;
handler.post(new Runnable() {
public void run() {
myLabel.setText(data);
}
});
}
else {
readBuffer[readBufferPosition++] = b;
}
}
}
}
catch (IOException ex) {
stopWorker = true;
}
}
}
});
workerThread.start();
}
void sendData() throws IOException {
String msg = myTextbox.getText().toString();
msg += "\n";
//mmOutputStream.write(msg.getBytes());
mmOutputStream.write('A');
myLabel.setText("Data Sent");
}
void closeBT() throws IOException {
stopWorker = true;
mmOutputStream.close();
mmInputStream.close();
mmSocket.close();
myLabel.setText("Bluetooth Closed");
}
private void showMessage(String theMsg) {
Toast msg = Toast.makeText(getBaseContext(),
theMsg, (Toast.LENGTH_LONG)/160);
msg.show();
}
}
Arduino Code:
#include <SoftwareSerial.h>
int bluetoothTx = 45;
int bluetoothRx = 47;
SoftwareSerial bluetooth(bluetoothTx, bluetoothRx);
void setup() {
//pinMode(45, OUTPUT);
//pinMode(47, INPUT);
pinMode(53, OUTPUT);
//Setup usb serial connection to computer
Serial.begin(9600);
//Setup Bluetooth serial connection to android
bluetooth.begin(115200);
bluetooth.print("$$$");
delay(100);
bluetooth.println("U,9600,N");
bluetooth.begin(9600);
}
void loop() {
//Read from bluetooth and write to usb serial
if(bluetooth.available()) {
char toSend = (char)bluetooth.read();
Serial.print(toSend);
flashLED();
}
//Read from usb serial to bluetooth
if(Serial.available()) {
char toSend = (char)Serial.read();
bluetooth.print(toSend);
flashLED();
}
}
void flashLED() {
digitalWrite(53, HIGH);
delay(500);
digitalWrite(53, LOW);
}
I've tried using 115200 and 9600 for the baud rates, and I've tried setting the bluetooth rx and tx pins as input/output and output/input. The Arduino is receiving serial data from the PC but can't send it to the Android (I can see this because of the flashLED() method).
The Android can't send any data at all to the Arduino. However they are both connected because the green light on the modem turns on and goes off and the red led flashes when I close the connection. The sendData() method doesn't throw an exception because otherwise showMessage("SEND FAILED"); would appear.
I also have this in my manifest .xml
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" />
Any help would be greatly appreciated!
Code taken from:
http://bellcode.wordpress.com/2012/01/02/android-and-arduino-bluetooth-communication/
Just solved the problem for anyone else who came across this page.
Seems that my Arduino doesn't like me using digital pins for serial communication, I use TX and RX instead with this code taken from http://jondontdoit.blogspot.com.au/2011/11/bluetooth-mate-tutorial.html, also seems that 9600 is a good baud instead of 115200.
/***********************
Bluetooth test program
***********************/
//TODO
//TEST THIS PROGRAM WITH ANDROID,
//CHANGE PINS TO RX AND TX THO ON THE ARDUINO!
//int counter = 0;
int 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.read();
// if it's a capital R, reset the counter
if (incomingByte == 'g') {
digitalWrite(53, HIGH);
delay(500);
digitalWrite(53, LOW);
delay(500);
//Serial.println("RESET");
//counter=0;
}
}
//Serial.println(counter);
//counter++;
//delay(250);
}
I was getting the same thing. I went into 'Settings'->'Wireless and Networks'->'Bluetooth Settings' and paired the device. When I went back and re-ran my code, it connected, no exception. I put controls in my UI for displaying the paired devices, I'm going to see if I can code to manage pairing devices from my UI.
I was able to get this to run only after replacing this section:
Set<BluetoothDevice> pairedDevices = BluetoothAdapter.getBondedDevices();
if(pairedDevices.size() > 0)
{
for(BluetoothDevice device : pairedDevices)
{
if(device.getName().startsWith("FireFly-"))
{
mmDevice = device;
Log.d("ArduinoBT", "findBT found device named " + mmDevice.getName());
Log.d("ArduinoBT", "device address is " + mmDevice.getAddress());
break;
}
}
}
with this:
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
mmDevice = mBluetoothAdapter.getRemoteDevice("00:06:66:46:5A:91");
if (pairedDevices.contains(mmDevice))
{
statusText.setText("Bluetooth Device Found, address: " + mmDevice.getAddress() );
Log.d("ArduinoBT", "BT is paired");
}
where I entered the address of my Bluetooth device.
The original code finds the device and returns the correct address, but
mmSocket.connect();
generates an exception "java.io.IOException: Service discovery failed"
Suggestions?
For anyone who finds this page, but is stuck using a hardcoded mac address as above, set mac address to NULL, and insert this code into OnResume()
try{
File f = new File(Environment.getExternalStorageDirectory()+"/mac.txt");
FileInputStream fileIS = new FileInputStream(f);
buf = new BufferedReader(new InputStreamReader(fileIS));
String readString = new String();
while((readString = buf.readLine())!= null){
address = readString;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
ALSO, don't forget to allow eclipse to include necessary libraries, and place your mac address into mac.txt on the root of the SD Card, then you can simply give users a text file with their mac address while still allowing the app to be downloaded from the market without customizing every instance.
#Backwards_Dave just for a curious, try to connect to 45 and 46 pins and use this simple code. I use it and have no problem. You will be able to send data from Arduino Serial Monitor and read it there.
/*
Pinout:
45 --> BT module Tx
46 --> BT module Rx
*/
#include <SoftwareSerial.h>
SoftwareSerial mySerial(45, 46); // RX, TX
void setup()
{
// Open serial communications and wait for port to open:
Serial.begin(9600);
Serial.println("I am ready to send some stuff!");
// set the data rate for the SoftwareSerial port
mySerial.begin(9600);
}
void loop() // run over and over
{
if (mySerial.available())
Serial.write(mySerial.read());
if (Serial.available())
mySerial.write(Serial.read());
}
Also, what BlueTooth shield are you using for Arduino? HC-06?
EDIT
Just tested it with Mega2560(don't have 1280) and it works with no problem.
I believe problem was with pinout.
Waiting for your feedback
If you're still looking for an answer, try changing the software serial pins. This is a well known limitation of the library you are using.
Not all pins on the Mega support change interrupts, so only the following can be used for RX: 10, 11, 12, 13, 14, 15, 50, 51, 52, 53, A8 (62), A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), A15 (69). Refs
Hope this helps.
I think it might be some fault in Bluetooth.. its better to re-install its drivers.. as the code given above looks correct.
I'm programming a simple Bluetooth client to send and receive text messages throught RFCOMM as a serial port. I had a look at the Android SDK tutorials and did it in the same way: an Activity which calls a thread to make the connection, and once done, another thread to take care of msg reception.
I'm trying to connect to a Parallax EasyBluetooth. Connection works all right between computer and EasyBT, and also between a Java based mobile and the EasyBT. So the problem must be at the code or, I hope not, at the Android mobile bluetooth chip. Anyway it gets on and off, and detects other devices when scanning, so I guess problem is just my coding.
The problem is that the code gets stuck at the connect() method. So let's see if anyone knows why.
The XML for the activity is simple:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/hello"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/boton"
android:id="#+id/boton_enviar"
/>
</LinearLayout>
Of course I have added the bluetooth permissions to the Manifiest:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
And the code is the following:
package uniovi.PFC;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
public class PruebaBTActivity extends Activity {
private String TAG = "pruebaBT";
private BluetoothAdapter mBluetoothAdapter;
private Map<String, BluetoothDevice> mArrayAdapter;
private ConnectedThread hiloEscuchas;
private ConnectThread hiloConectando;
private Handler mHandler;
private Button botonEnviar;
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private static final int REQUEST_ENABLE_BT = 1;
private static final int MESSAGE_READ = 1;
private byte bytes_enviar[];
private String cmd;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.d(TAG, "On create abriendo");
mArrayAdapter = new HashMap<String, BluetoothDevice>();
botonEnviar = (Button)findViewById(R.id.boton_enviar);
botonEnviar.setEnabled(false);
botonEnviar.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
cmd = "A";
bytes_enviar = cmd.getBytes();
hiloEscuchas.write(bytes_enviar);
}
});
Log.d(TAG, "On create cerrando");
}
#Override
public void onResume() {
super.onResume();
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
Log.d(TAG, "Device does not support Bluetooth");
}
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.put(device.getName(), device);
}
}
BluetoothDevice device = mArrayAdapter.get("EasyBT");
hiloConectando = new ConnectThread(device);
hiloConectando.run();
//while(hiloEscuchas.isConnected()==false);
//botonEnviar.setEnabled(true);
}
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// Do work to manage the connection (in a separate thread)
hiloEscuchas = new ConnectedThread(mmSocket);
hiloEscuchas.run();
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private boolean conectado;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
conectado = false;
mHandler = new Handler();
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
conectado = true;
}
public boolean isConnected(){
return conectado;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
cmd = "A";
bytes_enviar = cmd.getBytes();
hiloEscuchas.write(bytes_enviar);
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main Activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main Activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
}
There was some code to make a bluetooth scan for devices, but in order to get things simple until it works, I wrote the MAC address manually into a variable. Comments explain this, and also shows where it gets stuck.
Thanks
I think these are causing at least part of your problem:
hiloConectando = new ConnectThread(device);
hiloConectando.run();
and
hiloEscuchas = new ConnectedThread(mmSocket);
hiloEscuchas.run();
You should be calling start(), not run(), e.g.:
hiloConectando = new ConnectThread(device);
hiloConectando.start();
I am trying to build an Android application that will interface with an external GPS receiver via the Bluetooth Serial Port Profile (SPP). I am using a Nexus One running 2.3.3. I have managed to get my application to receive data from GPS, but I have two issues: 1) When I connect to the device, it only works some of the time. Sometimes the connection just times out, other times it says the device is busy or in use. 2) I haven't been able to figure out how to send data back to the device, which is probably an issue of how I'm using the streams since the incoming stream is a blocking call.
I moved just the relevant code to a new Android application for testing, which is the following:
/res/layout/main.xml (two buttons and a textview)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
<Button android:id="#+id/btnStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Connect"></Button>
<Button android:id="#+id/btnSend" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send Message"></Button>
<TextView android:id="#+id/textStatus" android:textSize="24sp" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Status Goes Here" />
</LinearLayout>
/src/com.example.bluetoothspp/MainActivity.java
package com.example.bluetoothspp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.SocketTimeoutException;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private static final String BTAG = "BTThread";
static final int MSG_BT_GOT_DATA = 1;
static final int MSG_BT_STATUS_MSG = 2;
static final int MSG_BT_FINISHED = 99;
Button btnStart, btnSend;
TextView textStatus;
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothDevice btdevice = null;
Thread bThread;
BluetoothSocket bsocket;
InputStream bis = null; //Bluetooth input stream
OutputStream bos = null; //Bluetooth output stream
private String MACAddress = "00:01:95:06:1F:32";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnStart = (Button)findViewById(R.id.btnStart);
btnSend = (Button)findViewById(R.id.btnSend);
textStatus = (TextView)findViewById(R.id.textStatus);
btnStart.setOnClickListener(btnStartListener);
btnSend.setOnClickListener(btnSendListener);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
private OnClickListener btnStartListener = new OnClickListener() {
public void onClick(View v){
if(btnStart.getText().equals("Connect")){
Log.i(TAG, "Connect button pressed");
if (mBluetoothAdapter == null) { //No adapter. Fail
Log.e(TAG, "getDefaultAdapter returned null");
textStatus.setText("getDefaultAdapter returned null");
} else {
if (!mBluetoothAdapter.isEnabled()) { //Bluetooth disabled
Log.e(TAG, "Bluetooth is Disabled");
textStatus.setText("Bluetooth is Disabled");
} else {
Log.i(TAG, "Connecting to Device: " + MACAddress);
btdevice = mBluetoothAdapter.getRemoteDevice(MACAddress);
Log.i(TAG, "Device: " + btdevice.getName());
Log.i(TAG, "Trying to Connect...");
textStatus.setText("Trying to Connect...");
Log.i(TAG, "Starting Thread");
try {
bThread = new Thread(new BluetoothClient(btdevice, true));
bThread.start();
} catch (IOException e) {
Log.e(TAG, "Could not create thread for bluetooth: " + e);
textStatus.setText("Could not create thread for bluetooth...");
}
btnStart.setText("Disconnect");
}
}
} else {
Log.i(TAG, "Disconnect button pressed");
btnStart.setText("Connect");
}
}
};
private OnClickListener btnSendListener = new OnClickListener() {
public void onClick(View v){
textStatus.setText("Sending Message to Thread.");
SendDataToBluetooth("something\r\n");
}
};
public class BluetoothClient implements Runnable {
public BluetoothClient(BluetoothDevice device, boolean IsAnHTCDevice) throws IOException {
if (IsAnHTCDevice) {
//This is a workaround for HTC devices, but it likes to throw an IOException "Connection timed out"
try {
Method m = device.getClass().getMethod("createRfcommSocket", new Class[] {int.class});
bsocket = (BluetoothSocket) m.invoke(device, Integer.valueOf(1));
} catch (Exception e) {
Log.e(BTAG, "Error at HTC/createRfcommSocket: " + e);
e.printStackTrace();
handler.sendMessage(handler.obtainMessage(MSG_BT_STATUS_MSG, "MethodException: " + e));
}
} else {
//This is the normal method, but on a Nexus One it almost always throws an IOException "Service discovery failed" message
try {
UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
bsocket = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (Exception e) {
Log.e(BTAG, "Error at createRfcommSocketToServiceRecord: " + e);
e.printStackTrace();
handler.sendMessage(handler.obtainMessage(MSG_BT_STATUS_MSG, "MethodException: " + e));
}
}
}
public void run() {
try {
Log.i(BTAG, "Cancelling Discovery");
mBluetoothAdapter.cancelDiscovery();
Log.i(BTAG, "Connecting to Socket");
bsocket.connect();
bis = bsocket.getInputStream();
bos = bsocket.getOutputStream();
Log.i(BTAG, "Socket created, streams assigned");
handler.sendMessage(handler.obtainMessage(MSG_BT_STATUS_MSG, "Device Connected"));
Log.i(BTAG, "Waiting for data...");
byte[] buffer = new byte[4096];
int read = bis.read(buffer, 0, 4096); // This is blocking
Log.i(BTAG, "Getting data...");
while (read != -1) {
byte[] tempdata = new byte[read];
System.arraycopy(buffer, 0, tempdata, 0, read);
handler.sendMessage(handler.obtainMessage(MSG_BT_GOT_DATA, tempdata));
read = bis.read(buffer, 0, 4096); // This is blocking
}
} catch (SocketTimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(BTAG, "Finished");
handler.sendMessage(handler.obtainMessage(MSG_BT_FINISHED));
}
}
}
public void SendDataToBluetooth(String cmd) { // You run this from the main thread.
try {
if (bsocket != null) {
bos.write(cmd.getBytes());
}
} catch (Exception e) {
Log.e("SendDataToBluetooth", "Message send failed. Caught an exception: " + e);
}
}
public Handler handler = new Handler() { // Handler for data coming from the network and bluetooth sockets
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_BT_GOT_DATA:
Log.i("handleMessage", "MSG_BT_GOT_DATA: " + (String) msg.obj);
textStatus.setText((String) msg.obj);
break;
case MSG_BT_STATUS_MSG:
Log.i("handleMessage", "MSG_BT_STATUS_MSG: " + (String) msg.obj);
textStatus.setText((String) msg.obj);
break;
case MSG_BT_FINISHED:
Log.i("handleMessage", "MSG_BT_FINISHED");
btnStart.setText("Connect");
break;
default:
super.handleMessage(msg);
}
}
};
#Override
protected void onDestroy() {
super.onDestroy();
if (bThread != null) { // If the thread is currently running, close the socket and interrupt it.
Log.i(BTAG, "Killing BT Thread");
try {
bis.close();
bos.close();
bsocket.close();
bsocket = null;
} catch (IOException e) {
Log.e(BTAG, "IOException");
e.printStackTrace();
} catch (Exception e) {
Log.e(BTAG, "Exception");
e.printStackTrace();
}
try {
Thread moribund = bThread;
bThread = null;
moribund.interrupt();
} catch (Exception e) {}
Log.i(BTAG, "BT Thread Killed");
}
}
}
I found that using the normal "bsocket = device.createRfcommSocketToServiceRecord(MY_UUID);" method would usually result in a "Service discovery failed" message for me, so I also tried the "bsocket = (BluetoothSocket) m.invoke(device, Integer.valueOf(1));" method. That works more often, but likes to time out when I try to connect.
What am I doing wrong here?
Try listening to the incoming data and writing to the device in separate threads. This way you are separating blocking calls.
Did you have a look at Bluetooth chat sample? The sample uses the similar threading technique.
If you are targeting 2.3 and up (which is currently installed on over 50% of android devices out there) you can use the createInsecureRfcommSocketToServiceRecord method to communicate with the device which will surely make it better and more connectable.
Most of the network socket examples I found for Android were one directional only. I needed a solution for a bi-directional data stream. I eventually learned of the AsyncTask. This example shows how to get data from a socket and send data back to it. Due to the blocking nature of a socket that is receiving data, that blocking needs to run in a thread other than the UI thread.
For the sake of example, this code connects to a webserver. Pressing the "Start AsyncTask" button will open the socket. Once the socket is open, the web server waits for a request. Pressing the "Send Message" button will send a request to the server. Any response from the server will be displayed in the TextView. In the case of http, a web server will disconnect from the client once all the data has been sent. For other TCP data streams, the connection will stay up until one side disconnects.
Screenshot:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.exampleasynctask"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:icon="#drawable/icon" android:label="#string/app_name">
<activity android:name=".MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
res\layout\main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="#+id/btnStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Start AsyncTask"></Button>
<Button android:id="#+id/btnSend" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send Message"></Button>
<TextView android:id="#+id/textStatus" android:textSize="24sp" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Status Goes Here" />
</LinearLayout>
src\com.exampleasynctask\MainActivity.java:
package com.exampleasynctask;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
Button btnStart, btnSend;
TextView textStatus;
NetworkTask networktask;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnStart = (Button)findViewById(R.id.btnStart);
btnSend = (Button)findViewById(R.id.btnSend);
textStatus = (TextView)findViewById(R.id.textStatus);
btnStart.setOnClickListener(btnStartListener);
btnSend.setOnClickListener(btnSendListener);
networktask = new NetworkTask(); //Create initial instance so SendDataToNetwork doesn't throw an error.
}
private OnClickListener btnStartListener = new OnClickListener() {
public void onClick(View v){
btnStart.setVisibility(View.INVISIBLE);
networktask = new NetworkTask(); //New instance of NetworkTask
networktask.execute();
}
};
private OnClickListener btnSendListener = new OnClickListener() {
public void onClick(View v){
textStatus.setText("Sending Message to AsyncTask.");
networktask.SendDataToNetwork("GET / HTTP/1.1\r\n\r\n");
}
};
public class NetworkTask extends AsyncTask<Void, byte[], Boolean> {
Socket nsocket; //Network Socket
InputStream nis; //Network Input Stream
OutputStream nos; //Network Output Stream
#Override
protected void onPreExecute() {
Log.i("AsyncTask", "onPreExecute");
}
#Override
protected Boolean doInBackground(Void... params) { //This runs on a different thread
boolean result = false;
try {
Log.i("AsyncTask", "doInBackground: Creating socket");
SocketAddress sockaddr = new InetSocketAddress("192.168.1.1", 80);
nsocket = new Socket();
nsocket.connect(sockaddr, 5000); //10 second connection timeout
if (nsocket.isConnected()) {
nis = nsocket.getInputStream();
nos = nsocket.getOutputStream();
Log.i("AsyncTask", "doInBackground: Socket created, streams assigned");
Log.i("AsyncTask", "doInBackground: Waiting for inital data...");
byte[] buffer = new byte[4096];
int read = nis.read(buffer, 0, 4096); //This is blocking
while(read != -1){
byte[] tempdata = new byte[read];
System.arraycopy(buffer, 0, tempdata, 0, read);
publishProgress(tempdata);
Log.i("AsyncTask", "doInBackground: Got some data");
read = nis.read(buffer, 0, 4096); //This is blocking
}
}
} catch (IOException e) {
e.printStackTrace();
Log.i("AsyncTask", "doInBackground: IOException");
result = true;
} catch (Exception e) {
e.printStackTrace();
Log.i("AsyncTask", "doInBackground: Exception");
result = true;
} finally {
try {
nis.close();
nos.close();
nsocket.close();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Log.i("AsyncTask", "doInBackground: Finished");
}
return result;
}
public void SendDataToNetwork(String cmd) { //You run this from the main thread.
try {
if (nsocket.isConnected()) {
Log.i("AsyncTask", "SendDataToNetwork: Writing received message to socket");
nos.write(cmd.getBytes());
} else {
Log.i("AsyncTask", "SendDataToNetwork: Cannot send message. Socket is closed");
}
} catch (Exception e) {
Log.i("AsyncTask", "SendDataToNetwork: Message send failed. Caught an exception");
}
}
#Override
protected void onProgressUpdate(byte[]... values) {
if (values.length > 0) {
Log.i("AsyncTask", "onProgressUpdate: " + values[0].length + " bytes received.");
textStatus.setText(new String(values[0]));
}
}
#Override
protected void onCancelled() {
Log.i("AsyncTask", "Cancelled.");
btnStart.setVisibility(View.VISIBLE);
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
Log.i("AsyncTask", "onPostExecute: Completed with an Error.");
textStatus.setText("There was a connection error.");
} else {
Log.i("AsyncTask", "onPostExecute: Completed.");
}
btnStart.setVisibility(View.VISIBLE);
}
}
#Override
protected void onDestroy() {
super.onDestroy();
networktask.cancel(true); //In case the task is currently running
}
}
The SendDataToNetwork task runs in the main ui thread, meaning it will crash a Honeycomb or higher app due to NetworkOnMainThreadException Fatal exception. Here's what my SendDataToNetwork looks like to avoid this issue:
public boolean sendDataToNetwork(final byte[] cmd) {
if (_nsocket.isConnected()) {
Log.i(TAG, "SendDataToNetwork: Writing received message to socket");
new Thread(new Runnable() {
public void run() {
try {
_nos.write(cmd);
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "SendDataToNetwork: Message send failed. Caught an exception");
}
}
}).start();
return true;
}
Log.i(TAG, "SendDataToNetwork: Cannot send message. Socket is closed");
return false;
}
Your SendDataToNetwork does not run on the same thread as doInBackground(). There is a possibility that SendDataToNetwork would start sending data before socket is ready.
To avoid all this just use SendDataToNetwork to save data and signal to background thread that data is ready to be sent.
Since there is possibility that user can press button multiple times, while the old data is still being sent, you should have synchronized Queue inside NetworkTask. Then:
Background thread sets up the socket connection and then goes to sleep (via wait()).
On button press, SendDataToNetwork adds data to queue and wakes up the background thread (via notify()).
When background thread wakes up, it first checks the finish flag. If set, it closes connections and exits. If not it reads data from Queue, sends it to network and goes back to sleep.
You should have finish() method which sets a finish flag (atomic variable, like boolean) and wakes the background thread. This is a way to gracefully exit the background thread.
Take a look at how thread synchronization is done: http://www.jchq.net/tutorial/07_03Tut.htm
More interactive example
Similar to the OP's, but you can control host, port and message + there is a popup error notification if the connection failed.
Usage 1:
get Android and a Linux desktop on a LAN
find the IP of the desktop with ifconfig
run netcat -l 12345 on a terminal
on Android, fill in the IP of the desktop
click contact server
on the terminal, type the reply, and hit Ctrl + D
it appears on the output: section
Usage 2:
hostname google.com
port 80
Message: "GET / HTTP/1.1\r\nHost: google.com\r\n\r\n"
Note that some HTTP servers won't close after the reply expecting further requests, and the application will hang until they timeout. Such servers expect you to parse the Content-Width header and close yourself.
If the connection fails, an alert message is shown to the user on a dialog.
Code
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
And the main activity is:
import android.app.Activity;
import android.app.AlertDialog;
import android.app.IntentService;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Main extends Activity {
final static String TAG = "AndroidCheatSocket";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
TextView textView;
final String defaultHostname = "192.168.0.";
textView = new TextView(this);
textView.setText("hostname / IP:");
linearLayout.addView(textView);
final EditText hostnameEditText = new EditText(this);
hostnameEditText.setText(defaultHostname);
hostnameEditText.setSingleLine(true);
linearLayout.addView(hostnameEditText);
textView = new TextView(this);
textView.setText("port:");
linearLayout.addView(textView);
final EditText portEditText = new EditText(this);
portEditText.setText("12345");
portEditText.setSingleLine(true);
linearLayout.addView(portEditText);
textView = new TextView(this);
textView.setText("data to send:");
linearLayout.addView(textView);
final EditText dataEditText = new EditText(this);
dataEditText.setText(String.format("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", defaultHostname));
linearLayout.addView(dataEditText);
final TextView replyTextView = new TextView(this);
final ScrollView replyTextScrollView = new ScrollView(this);
replyTextScrollView.addView(replyTextView);
final Button button = new Button(this);
button.setText("contact server");
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
button.setEnabled(false);
new MyAsyncTask(Main.this, replyTextView, button).execute(
hostnameEditText.getText().toString(),
portEditText.getText().toString(),
dataEditText.getText().toString());
}
});
linearLayout.addView(button);
textView = new TextView(this);
textView.setText("output:");
linearLayout.addView(textView);
linearLayout.addView(replyTextScrollView);
this.setContentView(linearLayout);
}
private class MyAsyncTask extends AsyncTask<String, Void, String> {
Activity activity;
Button button;
TextView textView;
IOException ioException;
MyAsyncTask(Activity activity, TextView textView, Button button) {
super();
this.activity = activity;
this.textView = textView;
this.button = button;
this.ioException = null;
}
#Override
protected String doInBackground(String... params) {
StringBuilder sb = new StringBuilder();
try {
Socket socket = new Socket(
params[0],
Integer.parseInt(params[1]));
OutputStream out = socket.getOutputStream();
out.write(params[2].getBytes());
InputStream in = socket.getInputStream();
byte buf[] = new byte[1024];
int nbytes;
while ((nbytes = in.read(buf)) != -1) {
sb.append(new String(buf, 0, nbytes));
}
socket.close();
} catch(IOException e) {
this.ioException = e;
return "error";
}
return sb.toString();
}
#Override
protected void onPostExecute(String result) {
if (this.ioException != null) {
new AlertDialog.Builder(this.activity)
.setTitle("An error occurrsed")
.setMessage(this.ioException.toString())
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
} else {
this.textView.setText(result);
}
this.button.setEnabled(true);
}
}
}
On GitHub with build boilerplate.
I've also posted an Android server example at: https://stackoverflow.com/a/35745834/895245
Tested on Android 5.1.1, Sony Xperia 3 D6643.