package info.guardianproject.securereaderinterface.installer; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Set; import java.util.UUID; import java.util.Vector; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.content.Intent; import android.os.Handler; import android.os.Message; import android.util.Log; public class SecureBluetooth { public final static String LOGTAG = "SecureBluetooth"; public final static int EVENT_DATA_RECEIVED = 0; public final static int EVENT_CONNECTED = 1; public final static int EVENT_DISCONNECTED = 2; public final static int EVENT_OBJECT_RECEIVED = 3; public final static int REQUEST_ENABLE_BT = 10; public final static int REQUEST_ENABLE_BT_DISCOVERY = 11; /* Bluetooth */ public BluetoothAdapter btAdapter; private BluetoothDevice btDevice; // I made this up private final UUID uuidSpp = UUID.fromString("00001101-0000-1000-8000-00805F9B31337"); private BluetoothSocket socket; private ConnectedThread connectedThread; private boolean connected = false; SecureBluetoothEventListener sbel; Handler eventHandler; public SecureBluetooth() { try { btAdapter = BluetoothAdapter.getDefaultAdapter(); if (btAdapter == null) { Log.e(LOGTAG, "No Bluetooth Adapter found"); } } catch (Exception e) { Log.e(LOGTAG, "Couldn't get Bluetooth Adapter"); e.printStackTrace(); } // Handlers let us interact with threads on the UI thread // The handleMessage method receives messages from other threads and // will act upon it on the UI thread // This handler is to receive data from the connected thread and send // events eventHandler = new Handler() { @Override public void handleMessage(Message msg) { // We got a message which means that we have an // EVENT_DATA_RECEIVED to send // Or CONNECTED or DISCONNECTED.. // msg.arg1 will equal one of those if (sbel != null) { if (msg.what == EVENT_DATA_RECEIVED && msg.obj != null) { sbel.secureBluetoothEvent(msg.what, msg.arg1, msg.obj); } else if (msg.what == EVENT_OBJECT_RECEIVED && msg.obj != null) { sbel.secureBluetoothEvent(msg.what, -1, msg.obj); } else { Log.v(LOGTAG, "propigating event " + msg.what); sbel.secureBluetoothEvent(msg.what, -1, null); } } } }; } // This is the interface that must be implemented to get the callbacks public interface SecureBluetoothEventListener { public void secureBluetoothEvent(int eventType, int dataLength, Object data); } public void setSecureBluetoothEventListener(SecureBluetoothEventListener _sbel) { sbel = _sbel; } /** * Returns whether the Bluetooth adapter is enabled. * * @return true of false */ public boolean isEnabled() { if (btAdapter != null) return btAdapter.isEnabled(); else return false; } /** * Call to trigger the intent for enabling Bluetooth * * @return void */ public void enableBluetooth(Activity activity) { if (!isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } } /** * Returns a list of bonded (paired) devices. * * @param info * flag to control display of additional information (device * names and types) * @return String array */ public String[] list(boolean info) { Vector<String> list = new Vector<String>(); Set<BluetoothDevice> devices; try { devices = btAdapter.getBondedDevices(); // convert the devices 'set' into an array so that we can // perform string functions on it Object[] deviceArray = devices.toArray(); // step through it and assign each device in turn to // remoteDevice and then print it's name for (int i = 0; i < devices.size(); i++) { BluetoothDevice thisDevice = btAdapter.getRemoteDevice(deviceArray[i].toString()); String element = thisDevice.getAddress(); if (info) { element += "," + thisDevice.getName() + "," + thisDevice.getBluetoothClass().getMajorDeviceClass(); // extended // information } list.addElement(element); } } catch (UnsatisfiedLinkError e) { Log.e(LOGTAG, Log.getStackTraceString(e)); } catch (Exception e) { Log.e(LOGTAG, Log.getStackTraceString(e)); } String outgoing[] = new String[list.size()]; list.copyInto(outgoing); return outgoing; } /** * Returns a list of hardware (MAC) addresses of bonded (paired) devices. * * @return String array */ public String[] list() { return list(false); } /** * Returns the name of the connected remote device It not connected, returns * "-1" */ public String getRemoteName() { if (connected) { String info = btDevice.getName(); return (info); } else { return ("-1"); } } /** * Returns the name of the connected remote device It not connected, returns * "-1" */ public String getRemoteAddress() { if (connected) { String info = btDevice.getAddress(); return (info); } else { return ("-1"); } } /** * Start discovery for bluetooth devices */ public boolean startDiscovery() { if (isEnabled() && !btAdapter.isDiscovering()) { return btAdapter.startDiscovery(); } else { return false; } } /** * Enable our bluetooth to be discovered */ public void enableDiscovery(Activity activity) { if (isEnabled() && btAdapter.getScanMode() != btAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent enableBtDiscoveryIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); activity.startActivityForResult(enableBtDiscoveryIntent, REQUEST_ENABLE_BT_DISCOVERY); } } /** * Returns the name of the currently connected device. * * @return String */ public String getName() { if (btDevice != null) return btDevice.getName(); else return "-1"; } /** * Connects to a Bluetooth device. * * The connect() method will attempt to determine what type of device is * currently specified by mac and will select one of the following Service * Profile UUIDs accordingly. * <p> * Currently only Android-to-serial modem (Arduino) and Android- to-serial * port (computer) connections are supported. * <p> * * @param mac * - hardware (MAC) address of the remote device * @return boolean flag for if connection was successful */ public boolean connect(String mac) { /* Before we connect, make sure to cancel any discovery! */ if (btAdapter.isDiscovering()) { btAdapter.cancelDiscovery(); Log.i(LOGTAG, "Cancelled ongoing discovery"); } Log.v(LOGTAG, "About to connect to " + mac); /* Make sure we're using a real bluetooth address to connect with */ if (BluetoothAdapter.checkBluetoothAddress(mac)) { Log.v(LOGTAG, "It's a bluetooth device"); /* Get the remote device we're trying to connect to */ btDevice = btAdapter.getRemoteDevice(mac); Log.v(LOGTAG, "Have the device"); /* Create the RFCOMM sockets */ try { Log.v(LOGTAG, "Trying the service"); socket = btDevice.createRfcommSocketToServiceRecord(uuidSpp); Log.i(LOGTAG, "connecting to service " + uuidSpp); socket.connect(); Log.v(LOGTAG, "Connected, moving on"); Log.i(LOGTAG, "Connected to device " + btDevice.getName() + " [" + btDevice.getAddress() + "]"); connected = true; // Start the thread to manage the connection and perform // transmissions connectedThread = new ConnectedThread(); connectedThread.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Message msg = eventHandler.obtainMessage(SecureBluetooth.EVENT_CONNECTED); eventHandler.sendMessage(msg); } catch (IOException e) { Log.e(LOGTAG, "Couldn't get a connection"); e.printStackTrace(); Message msg = eventHandler.obtainMessage(SecureBluetooth.EVENT_DISCONNECTED); eventHandler.sendMessage(msg); connected = false; } } else { Log.i(LOGTAG, "Address is not Bluetooth, please verify MAC."); Message msg = eventHandler.obtainMessage(SecureBluetooth.EVENT_DISCONNECTED); eventHandler.sendMessage(msg); connected = false; } return connected; } /** * Opens a BluetoothServerSocket to listen for connections Primarily * intended for Android-to-Android connections * * @return */ public void listen() { AcceptThread listenThread = new AcceptThread(); listenThread.start(); } /** * This thread runs while listening for incoming connections. It behaves * like a server-side client. It runs until a connection is accepted (or * until cancelled). * * Based on the Android BluetoothChat example */ private class AcceptThread extends Thread { // The local server socket BluetoothServerSocket mServerSocket = null; public AcceptThread() { // Create a new listening server socket try { mServerSocket = btAdapter.listenUsingRfcommWithServiceRecord("SerialPortProfile", uuidSpp); } catch (IOException e) { Log.e(LOGTAG, "Socket listen() failed", e); } } @Override public void run() { Log.v(LOGTAG, "running AcceptThread"); socket = null; // Listen to the server socket if we're not connected while (!connected) { Log.v(LOGTAG, "waiting for connection"); try { // This is a blocking call and will only return on a // successful connection or an exception socket = mServerSocket.accept(); btDevice = socket.getRemoteDevice(); } catch (Exception e) { Log.e(LOGTAG, "Socket accept() failed", e); break; } // If a connection was accepted if (socket != null) { Log.v(LOGTAG, "Socket accept() succeeded, moving on"); try { Message msg = eventHandler.obtainMessage(SecureBluetooth.EVENT_CONNECTED); eventHandler.sendMessage(msg); // Situation normal. Start the connected thread. connectedThread = new ConnectedThread(); connectedThread.start(); // Set the status connected = true; Log.i(LOGTAG, "Connected to device " + btDevice.getName() + " [" + btDevice.getAddress() + "]"); } catch (Exception ex) { Log.i(LOGTAG, "Couldn't get a connection"); ex.printStackTrace(); connected = false; } } } } } /** * Writes a byte[] buffer to the output stream. * * @param buffer */ public void write(byte[] buffer) { Log.v(LOGTAG, "Writing buffer " + buffer.length); connectedThread.write(buffer); } public void writeLength(long length) { Log.v(LOGTAG, "Writing length " + length); connectedThread.writeLength(length); } /** * Disconnects the Bluetooth socket. * * This should be called in the pause() and stop() methods inside the sketch * in order to ensure that the socket is properly closed when the sketch is * not running. The connection should be re-established in a resume() method * if the sketch loses and then regains focus. * * @see connect() */ public void disconnect() { Log.v(LOGTAG, "Disconnect Called"); if (connected) { // This should do it.. if (connectedThread != null) { Log.v(LOGTAG, "Calling cancel on connectThread"); connectedThread.cancel(); } connected = false; Message msg = eventHandler.obtainMessage(EVENT_DISCONNECTED); eventHandler.sendMessage(msg); } } public class ConnectedThread extends Thread { final InputStream inStream; final OutputStream outStream; DataInputStream dataIn; DataOutputStream dataOut; long readLength = 100; long totalRead = 0; public ConnectedThread() { InputStream tmpIn = null; OutputStream tmpOut = null; // Get the input and output streams, using temp objects because // member streams are final try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { Log.e(LOGTAG, "Error getting Input or Output stream"); e.printStackTrace(); } inStream = tmpIn; outStream = tmpOut; dataIn = new DataInputStream(inStream); dataOut = new DataOutputStream(outStream); } @Override public void run() { Log.i(LOGTAG, "ConnectedThread running"); // first going to read the first 4 bytes to get length try { readLength = dataIn.readLong(); Log.v(LOGTAG, "readLength is " + readLength); } catch (IOException e) { Log.v(LOGTAG, "Got IOException reading the length"); } byte[] buffer = new byte[256]; // buffer store for the stream int bytes; // bytes returned from read() byte[] transfer; // Keep listening to the InputStream until an exception occurs while (true) { try { // Read from the InputStream Log.v(LOGTAG, "going to read from inStream"); //bytes = inStream.read(buffer); bytes = dataIn.read(buffer); totalRead += bytes; Log.v(LOGTAG, "got " + bytes + " bytes, total " + totalRead + " of " + readLength); transfer = new byte[bytes]; System.arraycopy(buffer, 0, transfer, 0, bytes); // Send the obtained bytes to the UI Activity Message msg = eventHandler.obtainMessage(EVENT_DATA_RECEIVED, bytes, -1, transfer); eventHandler.sendMessage(msg); if (totalRead >= readLength) { Log.v(LOGTAG,"totalRead >= readLength, finished reading"); break; } } catch (IOException e) { Log.v(LOGTAG, "Got IOException, closing things up"); e.printStackTrace(); break; } } Log.v(LOGTAG, "Calling cancel"); cancel(); } /* Call this from the main activity to shutdown the connection */ public void cancel() { Log.v(LOGTAG,"ConnectedThread cancel"); /* Close the streams */ try { Log.v(LOGTAG, "Closing the streams"); outStream.flush(); dataIn.close(); inStream.close(); outStream.close(); Log.v(LOGTAG, "Closed Streams"); } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); Log.v(LOGTAG, "Closed socket"); Message msg = eventHandler.obtainMessage(EVENT_DISCONNECTED); eventHandler.sendMessage(msg); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /* * public void writeObject(Object objectToWrite) { try { * ObjectOutputStream oos = new ObjectOutputStream(outStream); * oos.writeObject(objectToWrite); } catch (IOException e) { // TODO * Auto-generated catch block e.printStackTrace(); } } */ /* Call this from the main Activity to send data to the remote device */ public void write(byte[] bytes) { try { //outStream.write(bytes); dataOut.write(bytes); Log.v(LOGTAG, "wrote " + bytes.length); } catch (IOException e) { e.printStackTrace(); } } public void writeLength(long length) { try { dataOut.writeLong(length); } catch (IOException e) { e.printStackTrace(); } } } }