diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index 84b977e1..a67d44f1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -42,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; +import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble.PebbleLESupport; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; @@ -75,6 +76,8 @@ class PebbleIoThread extends GBDeviceIoThread { private Socket mTCPSocket = null; // for emulator private InputStream mInStream = null; private OutputStream mOutStream = null; + private PebbleLESupport mPebbleLESupport; + private boolean mQuit = false; private boolean mIsConnected = false; private boolean mIsInstalling = false; @@ -180,12 +183,11 @@ class PebbleIoThread extends GBDeviceIoThread { mIsTCP = false; BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(btDeviceAddress); if (btDevice.getType() == BluetoothDevice.DEVICE_TYPE_LE) { - LOG.info("Ok this seems to be a LE Pebble, will try something that does not work :P"); - mInStream = new PipedInputStream(); // fake so that io blocks - mOutStream = new PipedOutputStream(); // fake so that io blocks - //new PebbleLESupport(this.getContext(),btDeviceAddress,(PipedInputStream)mInStream,(PipedOutputStream)mOutStream); // secret branch :P - } - else { + LOG.info("Ok this seems to be a LE Pebble, try LE Support, trouble ahead!"); + mInStream = new PipedInputStream(); + mOutStream = new PipedOutputStream(); + mPebbleLESupport = new PebbleLESupport(this.getContext(),btDeviceAddress,(PipedInputStream)mInStream,(PipedOutputStream)mOutStream); // secret branch :P + } else { ParcelUuid uuids[] = btDevice.getUuids(); if (uuids == null) { return false; @@ -708,6 +710,7 @@ class PebbleIoThread extends GBDeviceIoThread { } catch (IOException e) { e.printStackTrace(); } + mBtSocket = null; } if (mTCPSocket != null) { try { @@ -715,6 +718,11 @@ class PebbleIoThread extends GBDeviceIoThread { } catch (IOException e) { e.printStackTrace(); } + mTCPSocket = null; + } + if (mPebbleLESupport != null) { + mPebbleLESupport.close(); + mPebbleLESupport = null; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java new file mode 100644 index 00000000..942409f4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java @@ -0,0 +1,219 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE; + + +class PebbleGATTClient extends BluetoothGattCallback { + + private static final Logger LOG = LoggerFactory.getLogger(PebbleGATTClient.class); + + private static final UUID SERVICE_UUID = UUID.fromString("0000fed9-0000-1000-8000-00805f9b34fb"); + private static final UUID CONNECTIVITY_CHARACTERISTIC = UUID.fromString("00000001-328E-0FBB-C642-1AA6699BDADA"); + private static final UUID PAIRING_TRIGGER_CHARACTERISTIC = UUID.fromString("00000002-328E-0FBB-C642-1AA6699BDADA"); + private static final UUID MTU_CHARACTERISTIC = UUID.fromString("00000003-328e-0fbb-c642-1aa6699bdada"); + private static final UUID CONNECTION_PARAMETERS_CHARACTERISTIC = UUID.fromString("00000005-328E-0FBB-C642-1AA6699BDADA"); + private static final UUID CHARACTERISTIC_CONFIGURATION_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + + private final String mBtDeviceAddress; + private final BluetoothDevice mBtDevice; + private final Context mContext; + + private boolean oldPebble = false; + private boolean doPairing = true; + private boolean removeBond = false; + private BluetoothGatt mBluetoothGatt; + + PebbleGATTClient(Context context, BluetoothDevice btDevice) { + mContext = context; + mBtDevice = btDevice; + mBtDeviceAddress = btDevice.getAddress(); + } + + boolean initialize() { + connectToPebble(mBtDevice); + return true; + } + + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + if (!gatt.getDevice().getAddress().equals(mBtDeviceAddress)) { + LOG.info("onCharacteristicChanged() unexpected device: " + gatt.getDevice().getAddress() + " , expected: " + mBtDeviceAddress); + return; + } + LOG.info("onCharacteristicChanged()" + characteristic.getUuid().toString() + " " + GB.hexdump(characteristic.getValue(), 0, -1)); + } + + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + if (!gatt.getDevice().getAddress().equals(mBtDeviceAddress)) { + LOG.info("onCharacteristicRead() unexpected device: " + gatt.getDevice().getAddress() + " , expected: " + mBtDeviceAddress); + return; + } + LOG.info("onCharacteristicRead() status = " + status); + if (status == BluetoothGatt.GATT_SUCCESS) { + LOG.info("onCharacteristicRead()" + characteristic.getUuid().toString() + " " + GB.hexdump(characteristic.getValue(), 0, -1)); + + if (oldPebble) { + subscribeToConnectivity(gatt); + } else { + subscribeToConnectionParams(gatt); + } + } + } + + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + if (!gatt.getDevice().getAddress().equals(mBtDeviceAddress)) { + LOG.info("onConnectionStateChange() unexpected device: " + gatt.getDevice().getAddress() + " , expected: " + mBtDeviceAddress); + return; + } + LOG.info("onConnectionStateChange() status = " + status + " newState = " + newState); + if (newState == BluetoothGatt.STATE_CONNECTED) { + LOG.info("calling discoverServices()"); + gatt.discoverServices(); + + } + } + + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + if (!gatt.getDevice().getAddress().equals(mBtDeviceAddress)) { + LOG.info("onCharacteristcsWrite unexpected device: " + gatt.getDevice().getAddress() + " , expected: " + mBtDeviceAddress); + return; + } + LOG.info("onCharacteristicWrite() " + characteristic.getUuid()); + if (characteristic.getUuid().equals(PAIRING_TRIGGER_CHARACTERISTIC) || characteristic.getUuid().equals(CONNECTIVITY_CHARACTERISTIC)) { + mBtDevice.createBond(); // did not work when last tried + + if (oldPebble) { + subscribeToConnectivity(gatt); + } else { + subscribeToConnectionParams(gatt); + } + } + } + + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor bluetoothGattDescriptor, int status) { + if (!gatt.getDevice().getAddress().equals(mBtDeviceAddress)) { + LOG.info("onDescriptorWrite() unexpected device: " + gatt.getDevice().getAddress() + " , expected: " + mBtDeviceAddress); + return; + } + LOG.info("onDescriptorWrite() status=" + status); + + UUID CHARACTERISTICUUID = bluetoothGattDescriptor.getCharacteristic().getUuid(); + + if (CHARACTERISTICUUID.equals(CONNECTION_PARAMETERS_CHARACTERISTIC)) { + subscribeToConnectivity(gatt); + } else if (CHARACTERISTICUUID.equals(CONNECTIVITY_CHARACTERISTIC)) { + subscribeToMTU(gatt); + } else if (CHARACTERISTICUUID.equals(MTU_CHARACTERISTIC)) { + setMTU(gatt); + } + } + + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + if (!gatt.getDevice().getAddress().equals(mBtDeviceAddress)) { + LOG.info("onServicesDiscovered() unexpected device: " + gatt.getDevice().getAddress() + " , expected: " + mBtDeviceAddress); + return; + } + LOG.info("onServicesDiscovered() status = " + status); + if (status == BluetoothGatt.GATT_SUCCESS) { + BluetoothGattCharacteristic connectionPararmharacteristic = gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC); + oldPebble = connectionPararmharacteristic == null; + + if (oldPebble) { + LOG.info("This seems to be an older le enabled pebble"); + } + + if (doPairing) { + BluetoothGattCharacteristic characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(PAIRING_TRIGGER_CHARACTERISTIC); + if ((characteristic.getProperties() & PROPERTY_WRITE) != 0) { + characteristic.setValue(new byte[]{0, 1}); // bits 0=1 1=no slave sec 2=kk 3=samsung kk + gatt.writeCharacteristic(characteristic); + } + else { + LOG.info("This seems to be some <4.0 FW Pebble, reading pairing trigger"); + gatt.readCharacteristic(characteristic); + } + } else { + if (oldPebble) { + subscribeToConnectivity(gatt); + } else { + subscribeToConnectionParams(gatt); + } + } + } + } + + private void connectToPebble(BluetoothDevice btDevice) { + if (removeBond) { + try { + Method m = btDevice.getClass() + .getMethod("removeBond", (Class[]) null); + m.invoke(btDevice, (Object[]) null); + } catch (Exception e) { + LOG.warn(e.getMessage()); + } + try { + Thread.sleep(1000); + } catch (InterruptedException ignore) { + } + } + if (mBluetoothGatt != null) { + this.close(); + } + mBtDevice.createBond(); + mBluetoothGatt = btDevice.connectGatt(mContext, false, this); + } + + private void subscribeToConnectivity(BluetoothGatt gatt) { + LOG.info("subscribing to connectivity characteristic"); + BluetoothGattDescriptor descriptor = gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTIVITY_CHARACTERISTIC).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR); + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + gatt.writeDescriptor(descriptor); + gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTIVITY_CHARACTERISTIC), true); + } + + private void subscribeToMTU(BluetoothGatt gatt) { + LOG.info("subscribing to mtu characteristic"); + BluetoothGattDescriptor descriptor = gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR); + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + gatt.writeDescriptor(descriptor); + gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC), true); + } + + private void subscribeToConnectionParams(BluetoothGatt gatt) { + LOG.info("subscribing to connection parameters characteristic"); + BluetoothGattDescriptor descriptor = gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR); + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + gatt.writeDescriptor(descriptor); + gatt.setCharacteristicNotification(gatt.getService(SERVICE_UUID).getCharacteristic(CONNECTION_PARAMETERS_CHARACTERISTIC), true); + } + + private void setMTU(BluetoothGatt gatt) { + LOG.info("setting MTU"); + BluetoothGattCharacteristic characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(MTU_CHARACTERISTIC); + BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR); + descriptor.setValue(new byte[]{0x0b, 0x01}); // unknown + gatt.writeCharacteristic(characteristic); + } + + public void close() { + if (mBluetoothGatt != null) { + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java new file mode 100644 index 00000000..81edbc60 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java @@ -0,0 +1,159 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattServer; +import android.bluetooth.BluetoothGattServerCallback; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothManager; +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +class PebbleGATTServer extends BluetoothGattServerCallback { + private static final Logger LOG = LoggerFactory.getLogger(PebbleGATTServer.class); + private static final UUID WRITE_CHARACTERISTICS = UUID.fromString("10000001-328E-0FBB-C642-1AA6699BDADA"); + private static final UUID READ_CHARACTERISTICS = UUID.fromString("10000002-328E-0FBB-C642-1AA6699BDADA"); + private static final UUID CHARACTERISTICS_CONFIGURATION_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + private static final UUID SERVER_SERVICE = UUID.fromString("10000000-328E-0FBB-C642-1AA6699BDADA"); + private static final UUID SERVER_SERVICE_BADBAD = UUID.fromString("BADBADBA-DBAD-BADB-ADBA-BADBADBADBAD"); + private final BluetoothDevice mBtDevice; + private final PebbleLESupport mPebbleLESupport; + private Context mContext; + private BluetoothGattServer mBluetoothGattServer; + private BluetoothGattCharacteristic writeCharacteristics; + + PebbleGATTServer(PebbleLESupport pebbleLESupport, Context context, BluetoothDevice btDevice) { + mContext = context; + mBtDevice = btDevice; + mPebbleLESupport = pebbleLESupport; + } + + boolean initialize() { + BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE); + + mBluetoothGattServer = bluetoothManager.openGattServer(mContext, this); + + BluetoothGattService pebbleGATTService = new BluetoothGattService(SERVER_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY); + pebbleGATTService.addCharacteristic(new BluetoothGattCharacteristic(READ_CHARACTERISTICS, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ)); + + writeCharacteristics = new BluetoothGattCharacteristic(WRITE_CHARACTERISTICS, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_WRITE); + + writeCharacteristics.addDescriptor(new BluetoothGattDescriptor(CHARACTERISTICS_CONFIGURATION_DESCRIPTOR, BluetoothGattDescriptor.PERMISSION_WRITE)); + pebbleGATTService.addCharacteristic(writeCharacteristics); + mBluetoothGattServer.addService(pebbleGATTService); + + + final BluetoothGattService badbadService = new BluetoothGattService(SERVER_SERVICE_BADBAD, BluetoothGattService.SERVICE_TYPE_PRIMARY); + badbadService.addCharacteristic(new BluetoothGattCharacteristic(SERVER_SERVICE_BADBAD, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ)); + mBluetoothGattServer.addService(badbadService); + return true; + } + + synchronized void sendDataToPebble(byte[] data) { + LOG.info("send data to pebble " + GB.hexdump(data, 0, -1)); + writeCharacteristics.setValue(data.clone()); + + + mBluetoothGattServer.notifyCharacteristicChanged(mBtDevice, writeCharacteristics, false); + + try { + Thread.sleep(100); // FIXME: bad bad, I mean BAAAD + } catch (InterruptedException ignore) { + } + } + + synchronized private void sendAckToPebble(int serial) { + LOG.info("send ack to pebble for serial " + serial); + + writeCharacteristics.setValue(new byte[]{(byte) (((serial << 3) | 1) & 0xff)}); + + mBluetoothGattServer.notifyCharacteristicChanged(mBtDevice, writeCharacteristics, false); + + try { + Thread.sleep(100); // FIXME: bad bad, I mean BAAAD + } catch (InterruptedException ignore) { + } + } + + public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { + if (!characteristic.getUuid().equals(READ_CHARACTERISTICS)) { + LOG.warn("unexpected read request"); + return; + } + + LOG.info("will send response to read request from device: " + device.getAddress()); + if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, new byte[]{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})) { + LOG.warn("error sending response"); + } + } + + + public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, + boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { + if (!characteristic.getUuid().equals(WRITE_CHARACTERISTICS)) { + LOG.warn("unexpected write request"); + return; + } + LOG.info("write request: offset = " + offset + " value = " + GB.hexdump(value, 0, -1)); + int header = value[0] & 0xff; + int command = header & 7; + int serial = header >> 3; + if (command == 0x01) { + LOG.info("got ACK for serial = " + serial); + } + if (command == 0x02) { // some request? + LOG.info("got command 0x02"); + if (value.length > 1) { + sendDataToPebble(new byte[]{0x03, 0x19, 0x19}); // no we dont know what that means + mPebbleLESupport.createPipedInputReader(); // FIXME: maybe not here + } else { + sendDataToPebble(new byte[]{0x03}); // no we dont know what that means + } + } else if (command == 0) { // normal package + LOG.info("got PPoGATT package serial = " + serial + " sending ACK"); + + sendAckToPebble(serial); + + mPebbleLESupport.writeToPipedOutputStream(value, 1, value.length - 1); + } + } + + public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { + LOG.info("Connection state change for device: " + device.getAddress() + " status = " + status + " newState = " + newState); + } + + public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, + boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { + + if (!descriptor.getCharacteristic().getUuid().equals(WRITE_CHARACTERISTICS)) { + LOG.warn("unexpected write request"); + return; + } + + LOG.info("onDescriptorWriteRequest() notifications enabled = " + (value[0] == 1)); + if (!this.mBluetoothGattServer.sendResponse(device, requestId, 0, offset, value)) { + LOG.warn("onDescriptorWriteRequest() error sending response!"); + } + } + + public void onServiceAdded(int status, BluetoothGattService service) { + LOG.info("onServiceAdded() status = " + status + " service = " + service.getUuid()); + } + + public void onNotificationSent(BluetoothDevice bluetoothDevice, int status) { + //LOG.info("onNotificationSent() status = " + status + " to device " + mmBtDevice.getAddress()); + } + + void close() { + mBluetoothGattServer.cancelConnection(mBtDevice); + mBluetoothGattServer.clearServices(); + mBluetoothGattServer.close(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java new file mode 100644 index 00000000..1d600019 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java @@ -0,0 +1,140 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattServerCallback; +import android.bluetooth.BluetoothManager; +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +public class PebbleLESupport extends BluetoothGattServerCallback { + private static final Logger LOG = LoggerFactory.getLogger(PebbleLESupport.class); + private PipeReader mPipeReader; + private PebbleGATTServer mPebbleGATTServer; + private PebbleGATTClient mPebbleGATTClient; + private PipedInputStream mPipedInputStream; + private PipedOutputStream mPipedOutputStream; + + public PebbleLESupport(Context context, final String btDeviceAddress, PipedInputStream pipedInputStream, PipedOutputStream pipedOutputStream) { + + mPipedInputStream = new PipedInputStream(); + mPipedOutputStream = new PipedOutputStream(); + try { + pipedOutputStream.connect(mPipedInputStream); + pipedInputStream.connect(mPipedOutputStream); + } catch (IOException e) { + LOG.warn("could not connect input stream"); + } + + BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); + BluetoothAdapter adapter = manager.getAdapter(); + BluetoothDevice btDevice = adapter.getRemoteDevice(btDeviceAddress); + mPebbleGATTServer = new PebbleGATTServer(this, context, btDevice); + mPebbleGATTServer.initialize(); + + mPebbleGATTClient = new PebbleGATTClient(context, btDevice); + mPebbleGATTClient.initialize(); + } + + void writeToPipedOutputStream(byte[] value, int offset, int count) { + try { + mPipedOutputStream.write(value, offset, count); + } catch (IOException e) { + LOG.warn("error writing to output stream"); + } + } + + public void close() { + destroyPipedInputReader(); + if (mPebbleGATTServer != null) { + mPebbleGATTServer.close(); + mPebbleGATTServer = null; + } + if (mPebbleGATTClient != null) { + mPebbleGATTClient.close(); + mPebbleGATTClient = null; + } + } + + void createPipedInputReader() { + if (mPipeReader == null) { + mPipeReader = new PipeReader(); + } + if (!mPipeReader.isAlive()) { + mPipeReader.start(); + } + } + + private void destroyPipedInputReader() { + if (mPipeReader != null) { + mPipeReader.quit(); + mPipeReader.interrupt(); + try { + mPipeReader.join(); + } catch (InterruptedException e) { + LOG.error(e.getMessage()); + } + mPipeReader = null; + } + } + + private class PipeReader extends Thread { + int mmSequence = 0; + private boolean mQuit = false; + + @Override + public void run() { + int MTU = 339 - 3; + byte[] buf = new byte[8192]; + int bytesRead; + while (!mQuit) { + try { + // this code is very similar to iothread, that is bad + // because we are the ones who prepared the buffer, there should be no + // need to do crazy stuff just to find out the PP boundaries again. + bytesRead = mPipedInputStream.read(buf, 0, 4); + while (bytesRead < 4) { + bytesRead += mPipedInputStream.read(buf, bytesRead, 4 - bytesRead); + } + + int length = (buf[0] & 0xff) << 8 | (buf[1] & 0xff); + bytesRead = mPipedInputStream.read(buf, 4, length); + + while (bytesRead < length) { + bytesRead += mPipedInputStream.read(buf, bytesRead + 4, length - bytesRead); + } + + + int payloadToSend = bytesRead + 4; + int srcPos = 0; + while (payloadToSend > 0) { + int chunkSize = (payloadToSend < (MTU - 1)) ? payloadToSend : MTU - 1; + byte[] outBuf = new byte[chunkSize + 1]; + outBuf[0] = (byte) ((mmSequence++ << 3) & 0xff); + System.arraycopy(buf, srcPos, outBuf, 1, chunkSize); + mPebbleGATTServer.sendDataToPebble(outBuf); + srcPos += chunkSize; + payloadToSend -= chunkSize; + } + + } catch (IOException e) { + LOG.warn("IO exception"); + mQuit = true; + break; + } + } + } + + void quit() { + mQuit = true; + } + } + +} +