diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900Constants.java index f86d0b9b8..af779fc2c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900Constants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/casiogb6900/CasioGB6900Constants.java @@ -64,6 +64,17 @@ public final class CasioGB6900Constants { public static final UUID FUNCTION_SWITCH_CHARACTERISTIC = UUID.fromString("26eb001e-b012-49a8-b1f8-394fb2032b0f"); public static final String MUSIC_MESSAGE = "Music"; + // Link Loss + + public static final UUID LINK_LOSS_SERVICE = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb"); + + // TxPower + + public static final UUID TX_POWER_SERVICE_UUID = UUID.fromString("00001804-0000-1000-8000-00805f9b34fb"); + public static final UUID TX_POWER_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002a07-0000-1000-8000-00805f9b34fb"); + // Settings + public static final UUID CASIO_SETTING_FOR_BLE_CHARACTERISTIC_UUID = UUID.fromString("26eb000f-b012-49a8-b1f8-394fb2032b0f"); + // Notification Types public static final byte CALL_NOTIFICATION_ID = 3; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java index 530255ea9..a25202330 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTServer.java @@ -17,6 +17,7 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900; +import android.app.PendingIntent; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; @@ -25,12 +26,20 @@ import android.bluetooth.BluetoothGattServerCallback; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.content.Context; +import android.content.Intent; +import android.support.v4.content.LocalBroadcastManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; class CasioGATTServer extends BluetoothGattServerCallback { private static final Logger LOG = LoggerFactory.getLogger(CasioGATTServer.class); @@ -142,6 +151,18 @@ class CasioGATTServer extends BluetoothGattServerCallback { if (newState == BluetoothGattServer.STATE_DISCONNECTED) { } + if (newState == BluetoothGattServer.STATE_CONNECTED) { + GBDevice.State devState = mDeviceSupport.getDevice().getState(); + Intent deviceCommunicationServiceIntent = new Intent(mContext, DeviceCommunicationService.class); + if (devState.equals(GBDevice.State.WAITING_FOR_RECONNECT) || devState.equals(GBDevice.State.NOT_CONNECTED)) { + LOG.info("Forcing re-connect because GATT server has been reconnected."); + deviceCommunicationServiceIntent.setAction(DeviceService.ACTION_CONNECT); + deviceCommunicationServiceIntent.putExtra(GBDevice.EXTRA_DEVICE, device); + LocalBroadcastManager.getInstance(mContext).sendBroadcast(deviceCommunicationServiceIntent); + //PendingIntent reconnectPendingIntent = PendingIntent.getService(mContext, 2, deviceCommunicationServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT); + //builder.addAction(R.drawable.ic_notification, context.getString(R.string.controlcenter_connect), reconnectPendingIntent); + } + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java index 2491a80c3..ecd668746 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGATTThread.java @@ -25,6 +25,7 @@ public class CasioGATTThread extends Thread { CasioGATTServer mServer = null; private static final Logger LOG = LoggerFactory.getLogger(CasioGATTThread.class); private boolean mStopFlag = false; + private final Object waitObject = new Object(); public CasioGATTThread(Context context, CasioGB6900DeviceSupport deviceSupport) { @@ -36,18 +37,25 @@ public class CasioGATTThread extends Thread { } @Override - public void run() - { - if(!mServer.initialize()) { + public void run() { + if (!mServer.initialize()) { LOG.error("Error initializing CasioGATTServer. Has the context been set?"); return; } - while(!mStopFlag) { - try { - wait(100); - } catch(Exception e) - { + long waitTime = 60 * 1000; + + while (!mStopFlag) { + synchronized (waitObject) { + try { + waitObject.wait(waitTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if (mStopFlag) { + break; } } mServer.close(); @@ -55,6 +63,9 @@ public class CasioGATTThread extends Thread { public void quit() { mStopFlag = true; + synchronized (waitObject) { + waitObject.notify(); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java index c085909d0..d82dc19fc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/casiogb6900/CasioGB6900DeviceSupport.java @@ -20,6 +20,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattService; import android.content.Context; import android.net.Uri; @@ -45,23 +46,24 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; +import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(CasioGB6900DeviceSupport.class); - public BluetoothGattCharacteristic mCasioCharact1 = null; - public BluetoothGattCharacteristic mCasioCharact2 = null; - public BluetoothGattCharacteristic mCasioCharact3 = null; - public BluetoothGattCharacteristic mCasioCharact4 = null; - public BluetoothGattCharacteristic mCasioCharact5 = null; - private CasioGATTThread mThread = null; + private ArrayList mCasioCharacteristics = new ArrayList(); + private CasioGATTThread mThread; + private CasioHandlerThread mHandlerThread = null; private MusicSpec mBufferMusicSpec = null; private MusicStateSpec mBufferMusicStateSpec = null; + private BluetoothGatt mBtGatt = null; public CasioGB6900DeviceSupport() { super(LOG); + addSupportedService(GattService.UUID_SERVICE_IMMEDIATE_ALERT); addSupportedService(CasioGB6900Constants.CASIO_VIRTUAL_SERVER_SERVICE); addSupportedService(CasioGB6900Constants.ALERT_SERVICE_UUID); addSupportedService(CasioGB6900Constants.CASIO_IMMEDIATE_ALERT_SERVICE_UUID); @@ -70,6 +72,8 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { addSupportedService(CasioGB6900Constants.WATCH_FEATURES_SERVICE_UUID); addSupportedService(CasioGB6900Constants.CASIO_PHONE_ALERT_STATUS_SERVICE); addSupportedService(CasioGB6900Constants.MORE_ALERT_SERVICE_UUID); + addSupportedService(CasioGB6900Constants.TX_POWER_SERVICE_UUID); + addSupportedService(CasioGB6900Constants.LINK_LOSS_SERVICE); mThread = new CasioGATTThread(getContext(), this); } @@ -80,6 +84,34 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { mThread.start(); } + @Override + public void dispose() { + LOG.info("Dispose"); + close(); + + super.dispose(); + } + + private void close() { + if (mHandlerThread != null) { + mHandlerThread.quit(); + mHandlerThread.interrupt(); + mHandlerThread = null; + } + + if(mThread != null) { + mThread.quit(); + mThread.interrupt(); + mThread = null; + } + } + + @Override + public void onServicesDiscovered(BluetoothGatt gatt) { + mBtGatt = gatt; + super.onServicesDiscovered(gatt); + } + @Override protected TransactionBuilder initializeDevice(TransactionBuilder builder) { LOG.info("Initializing"); @@ -87,24 +119,57 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { gbDevice.setState(GBDevice.State.INITIALIZING); gbDevice.sendDeviceUpdateIntent(getContext()); - mCasioCharact1 = getCharacteristic(CasioGB6900Constants.CASIO_A_NOT_COM_SET_NOT); - mCasioCharact2 = getCharacteristic(CasioGB6900Constants.CASIO_A_NOT_W_REQ_NOT); - mCasioCharact3 = getCharacteristic(CasioGB6900Constants.FUNCTION_SWITCH_CHARACTERISTIC); - mCasioCharact4 = getCharacteristic(CasioGB6900Constants.ALERT_LEVEL_CHARACTERISTIC_UUID); - mCasioCharact5 = getCharacteristic(CasioGB6900Constants.RINGER_CONTROL_POINT); + addCharacteristics(); builder.setGattCallback(this); - builder.notify(mCasioCharact1, true); - builder.notify(mCasioCharact2, true); - builder.notify(mCasioCharact3, true); - builder.notify(mCasioCharact4, true); - builder.notify(mCasioCharact5, true); + + enableNotifications(builder, true); + + configureWatch(builder); LOG.info("Initialization Done"); return builder; } + // FIXME: Replace hardcoded values by configuration + private void configureWatch(TransactionBuilder builder) { + if (mBtGatt == null) + return; + + byte value[] = new byte[]{GattCharacteristic.MILD_ALERT}; + + BluetoothGattService llService = mBtGatt.getService(CasioGB6900Constants.LINK_LOSS_SERVICE); + BluetoothGattCharacteristic charact = llService.getCharacteristic(CasioGB6900Constants.ALERT_LEVEL_CHARACTERISTIC_UUID); + builder.write(charact, value); + } + + private void addCharacteristics() { + mCasioCharacteristics.clear(); + mCasioCharacteristics.add(getCharacteristic(CasioGB6900Constants.CASIO_A_NOT_COM_SET_NOT)); + mCasioCharacteristics.add(getCharacteristic(CasioGB6900Constants.CASIO_A_NOT_W_REQ_NOT)); + mCasioCharacteristics.add(getCharacteristic(CasioGB6900Constants.FUNCTION_SWITCH_CHARACTERISTIC)); + mCasioCharacteristics.add(getCharacteristic(CasioGB6900Constants.ALERT_LEVEL_CHARACTERISTIC_UUID)); + mCasioCharacteristics.add(getCharacteristic(CasioGB6900Constants.RINGER_CONTROL_POINT)); + } + + public boolean enableNotifications(TransactionBuilder builder, boolean enable) { + for(BluetoothGattCharacteristic charact : mCasioCharacteristics) { + builder.notify(charact, enable); + } + return true; + } + + public void readTxPowerLevel() { + try { + TransactionBuilder builder = performInitialized("readTxPowerLevel"); + builder.read(getCharacteristic(CasioGB6900Constants.TX_POWER_LEVEL_CHARACTERISTIC_UUID)); + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("readTxPowerLevel failed: " + e.getMessage()); + } + } + private void writeCasioCurrentTime(TransactionBuilder builder) { byte[] arr = new byte[10]; Calendar cal = Calendar.getInstance(); @@ -172,6 +237,10 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { { case (byte) 1: LOG.info("Initialization done, setting state to INITIALIZED"); + if(mHandlerThread == null) { + mHandlerThread = new CasioHandlerThread(getDevice(), getContext(), this); + mHandlerThread.start(); + } gbDevice.setState(GBDevice.State.INITIALIZED); gbDevice.sendDeviceUpdateIntent(getContext()); handled = true; @@ -195,7 +264,7 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { { TransactionBuilder builder = createTransactionBuilder("writeCasioCurrentTime"); writeCasioCurrentTime(builder); - performImmediately(builder); + performConnected(builder.getTransaction()); handled = true; } catch (IOException e) { LOG.warn("handleTimeRequests::writeCasioCurrentTime failed: " + e.getMessage()); @@ -206,7 +275,7 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { { TransactionBuilder builder = createTransactionBuilder("writeCasioLocalTimeInformation"); writeCasioLocalTimeInformation(builder); - performImmediately(builder); + performConnected(builder.getTransaction()); handled = true; } catch (IOException e) { LOG.warn("handleTimeRequests::writeCasioLocalTimeInformation failed: " + e.getMessage()); @@ -221,7 +290,7 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { { TransactionBuilder builder = createTransactionBuilder("writeCasioVirtualServerFeature"); writeCasioVirtualServerFeature(builder); - performImmediately(builder); + performConnected(builder.getTransaction()); } catch (IOException e) { LOG.warn("handleServerFeatureRequests failed: " + e.getMessage()); } @@ -251,15 +320,35 @@ public class CasioGB6900DeviceSupport extends AbstractBTLEDeviceSupport { return handled; } + @Override + public boolean onCharacteristicRead(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { + + UUID characteristicUUID = characteristic.getUuid(); + byte[] data = characteristic.getValue(); + + if(data.length == 0) + return true; + + if(characteristicUUID.equals(CasioGB6900Constants.TX_POWER_LEVEL_CHARACTERISTIC_UUID)) { + String str = "onCharacteristicRead: Received power level: "; + for(int i=0; i. */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.casiogb6900; + +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +import nodomain.freeyourgadget.gadgetbridge.devices.casiogb6900.CasioGB6900Constants; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; + +public class CasioHandlerThread extends GBDeviceIoThread { + private static final Logger LOG = LoggerFactory.getLogger(CasioHandlerThread.class); + private boolean mQuit = false; + private CasioGB6900DeviceSupport mDeviceSupport; + private final Object waitObject = new Object(); + //private CasioGATTServer mServer = null; + + private int TX_PERIOD = 60; + + private Calendar mTxTime = GregorianCalendar.getInstance(); + + public CasioHandlerThread(GBDevice gbDevice, Context context, CasioGB6900DeviceSupport deviceSupport) { + super(gbDevice, context); + LOG.info("Initializing Casio Handler Thread"); + mQuit = false; + //mServer = new CasioGATTServer(context, deviceSupport); + mDeviceSupport = deviceSupport; + } + + @Override + public void run() { + mQuit = false; + + /* + if(!mServer.initialize()) { + LOG.error("Error initializing CasioGATTServer. Has the context been set?"); + return; + } + */ + + long waitTime = TX_PERIOD * 1000; + while (!mQuit) { + + if (waitTime > 0) { + synchronized (waitObject) { + try { + waitObject.wait(waitTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + if (mQuit) { + break; + } + + if (gbDevice.getState() == GBDevice.State.NOT_CONNECTED) { + quit(); + } + + Calendar now = GregorianCalendar.getInstance(); + + if (now.compareTo(mTxTime) > 0) { + requestTxPowerLevel(); + } + + now = GregorianCalendar.getInstance(); + waitTime = mTxTime.getTimeInMillis() - now.getTimeInMillis(); + } + + } + + public void requestTxPowerLevel() { + try { + mDeviceSupport.readTxPowerLevel(); + + } catch(Exception e) { + + } + + mTxTime = GregorianCalendar.getInstance(); + mTxTime.add(Calendar.SECOND, TX_PERIOD); + synchronized (waitObject) { + waitObject.notify(); + } + } + + @Override + public void quit() { + LOG.info("CasioHandlerThread: Quit Handler Thread"); + mQuit = true; + synchronized (waitObject) { + waitObject.notify(); + } + } + +}