diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java index 2ac346f40..dfe3a641e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java @@ -36,6 +36,7 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final String PREF_FIND_PHONE_DURATION = "prefs_find_phone_duration"; public static final String PREF_ALTITUDE = "watchxplus_altitude"; public static final String PREF_REPEAT = "watchxplus_repeat"; + public static final String PREF_IS_BP_CALIBRATED = "watchxplus_is_bp_calibrated"; // time format constants public static final byte ARG_SET_TIMEMODE_24H = 0x00; @@ -50,7 +51,9 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] CMD_RETRIEVE_DATA_CONTENT = new byte[]{(byte)0xF0, 0x12}; public static final byte[] CMD_REMOVE_DATA_CONTENT = new byte[]{(byte)0xF0, 0x32}; public static final byte[] CMD_BLOOD_PRESSURE_MEASURE = new byte[]{0x05, 0x0D}; - + public static final byte[] CMD_HEART_RATE_MEASURE = new byte[]{0x03, 0x23}; + public static final byte[] CMD_IS_BP_CALIBRATED = new byte[]{0x05, 0x0B}; + public static final byte[] CMD_BP_CALIBRATION = new byte[]{0x05, 0x0C}; public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06}; public static final byte[] CMD_NOTIFICATION_CANCEL = new byte[]{0x03, 0x04}; @@ -67,6 +70,7 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] RESP_SHAKE_SWITCH = new byte[]{0x08, 0x03, -0x6E}; public static final byte[] RESP_DISCONNECT_REMIND = new byte[]{0x08, 0x00, 0x11}; + public static final byte[] RESP_IS_BP_CALIBRATED = new byte[]{0x08, 0x05, 0x0B}; public static final byte[] RESP_AUTHORIZATION_TASK = new byte[]{0x01, 0x01, 0x05}; public static final byte[] RESP_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03}; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java index 2fb9a6179..71863c7e8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java @@ -37,6 +37,8 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { public static final int FindPhone_ON = -1; public static final int FindPhone_OFF = 0; + public static boolean isBPCalibrated = false; + protected static Prefs prefs = GBApplication.getPrefs(); @NonNull @@ -164,6 +166,10 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { }; } +/* +Prefs from device settings on main page + */ +// return saved time format public static byte getTimeMode(SharedPreferences sharedPrefs) { String timeMode = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h)); if (timeMode.equals(getContext().getString(R.string.p_timeformat_24h))) { @@ -172,6 +178,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { return WatchXPlusConstants.ARG_SET_TIMEMODE_12H; } } + // check if it is needed to toggle Lift Wrist to Sreen on public static boolean shouldEnableHeadsUpScreen(SharedPreferences sharedPrefs) { String liftMode = sharedPrefs.getString(WatchXPlusConstants.PREF_ACTIVATE_DISPLAY, getContext().getString(R.string.p_on)); @@ -185,19 +192,8 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { // WatchXPlus doesn't support scheduled intervals. Treat it as "on". return !lostReminder.equals(getContext().getString(R.string.p_off)); } -// read altitude from preferences - public static int getAltitude(String address) { - return (int) prefs.getInt(WatchXPlusConstants.PREF_ALTITUDE, 200); - } - -// read repeat call notification - public static int getRepeatOnCall(String address) { - return (int) prefs.getInt(WatchXPlusConstants.PREF_REPEAT, 1); - } - -// read repeat call preferences - public static int getRepeat = 0; +// find phone settings /** * @return {@link #FindPhone_OFF}, {@link #FindPhone_ON}, or the duration */ @@ -226,4 +222,31 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { } } } + +/* +Values from device specific settings page + */ +// read altitude from preferences + public static int getAltitude(String address) { + return (int) prefs.getInt(WatchXPlusConstants.PREF_ALTITUDE, 200); + } + +// read repeat call notification + public static int getRepeatOnCall(String address) { + return (int) prefs.getInt(WatchXPlusConstants.PREF_REPEAT, 1); + } + +/* +Other saved preferences + */ + public static byte getBPCalibrationStatus(SharedPreferences sharedPrefs) { + String timeMode = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h)); + if (timeMode.equals(getContext().getString(R.string.p_timeformat_24h))) { + return WatchXPlusConstants.ARG_SET_TIMEMODE_24H; + } else { + return WatchXPlusConstants.ARG_SET_TIMEMODE_12H; + } + } + + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index d61b8bb0f..331f7bc99 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -247,9 +247,12 @@ public class NotificationListener extends NotificationListenerService { return; } } + if (shouldIgnore(sbn)) { - LOG.info("Ignore notification"); - return; + if (!"com.sec.android.app.clockpackage".equals(sbn.getPackageName())) { // allow phone alarm notification + LOG.info("Ignore notification: " + sbn.getPackageName()); + return; + } } switch (GBApplication.getGrantedInterruptionFilter()) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java index 4e320c8ad..a0bdf88fb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java @@ -74,12 +74,14 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction; import nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.operations.InitOperation; import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils; @@ -167,7 +169,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { - String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title); String message = StringUtils.truncate(senderOrTitle, 14) + "\0"; @@ -178,23 +179,37 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { message += StringUtils.truncate(notificationSpec.body, 64); } - sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_DEFAULT, message, false); + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_DEFAULT, message); } - private void sendNotification(int notificationChannel, String notificationText, boolean repeat) { +// cancel notification +// cancel watch notification - stop vibration and turn off screen + private void cancelNotification() { + try { + getQueue().clear(); + TransactionBuilder builder = performInitialized("cancelNotification"); + byte[] bArr; + int mPosition = 1024; + int mMessageId = 0xFF; + bArr = new byte[6]; + bArr[0] = (byte) ((int) (mPosition >> 24)); + bArr[1] = (byte) ((int) (mPosition >> 16)); + bArr[2] = (byte) ((int) (mPosition >> 8)); + bArr[3] = (byte) ((int) mPosition); + bArr[4] = (byte) (mMessageId >> 8); + bArr[5] = (byte) mMessageId; + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_NOTIFICATION_CANCEL, + WatchXPlusConstants.WRITE_VALUE, + bArr)); + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("Unable to cancel notification", e); + } + } + + private void sendNotification(int notificationChannel, String notificationText) { try { - int on = 5000; // repeat delay ms - int test = 1; // repeat once - if (repeat) { - test = WatchXPlusDeviceCoordinator.getRepeatOnCall(getDevice().getAddress()); - if (test < 1) { - test = 1; - } - WatchXPlusDeviceCoordinator.getRepeat = 1; - } else { - test = 1; - WatchXPlusDeviceCoordinator.getRepeat = 0; - } TransactionBuilder builder = performInitialized("showNotification"); byte[] command = WatchXPlusConstants.CMD_NOTIFICATION_TEXT_TASK; byte[] text = notificationText.getBytes("UTF-8"); @@ -222,33 +237,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } messagePart[0] = (byte) notificationChannel; messagePart[1] = (byte) messageIndex; - for (int i = 0; i < test; i++) { // repeat call notification - if (notificationText != "stop") { - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(command, - WatchXPlusConstants.KEEP_ALIVE, - messagePart)); - if (WatchXPlusDeviceCoordinator.getRepeat == 1) { - builder.wait(on); - } else { - i = test; - break; - } - } else { //cancel text notification - i = test; - byte[] bArr; - int mPosition = 1024; - bArr = new byte[4]; - bArr[0] = (byte) ((int) (mPosition >> 24)); - bArr[1] = (byte) ((int) (mPosition >> 16)); - bArr[2] = (byte) ((int) (mPosition >> 8)); - bArr[3] = (byte) ((int) mPosition); - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_NOTIFICATION_CANCEL, - WatchXPlusConstants.WRITE_VALUE, - bArr)); - } - } + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.KEEP_ALIVE, + messagePart)); } builder.queue(getQueue()); } catch (IOException e) { @@ -256,6 +248,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } + private WatchXPlusDeviceSupport enableNotificationChannels(TransactionBuilder builder) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_NOTIFICATION_SETTINGS, @@ -413,9 +406,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { .enableNotificationChannels(builder) .enableDoNotDisturb(builder, false) .setFitnessGoal(builder) + .getBloodPressureCalibrationStatus(builder) .syncPreferences(builder); - //.setHeadsUpScreen(builder, true); - //.getSwitchStatus(builder); builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); builder.setGattCallback(this); @@ -424,7 +416,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onDeleteNotification(int id) { - + cancelNotification(); } @Override @@ -475,21 +467,105 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } int repeat = 0; + boolean isRinging = false; @Override - public void onSetCallState(CallSpec callSpec) { - SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit(); + public void onSetCallState(final CallSpec callSpec) { + int repeatDelay = 5000; + int repeatCount = WatchXPlusDeviceCoordinator.getRepeatOnCall(getDevice().getAddress()); + if (repeatCount < 0) { + repeatCount = 0; + } + if (repeatCount > 5) { + repeatCount = 5; + } + + if("Phone".equals(callSpec.name)) { // ignore notification if caller name is Phone + return; + } + switch (callSpec.command) { case CallSpec.CALL_INCOMING: - WatchXPlusDeviceCoordinator.getRepeat = 1; - sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name, true); + isRinging = true; + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); +// TODO dirty code, need to fix +// repeat call notification up to 5 times + Handler handler = new Handler(); + if (repeatCount > 0) { + handler.postDelayed(new Runnable() { + public void run() { + // Actions to do after 5 seconds + if (isRinging) { + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); + } + } + }, repeatDelay); + } + if (repeatCount > 1) { + handler.postDelayed(new Runnable() { + public void run() { + // Actions to do after 5 seconds + if (isRinging) { + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); + } + } + }, repeatDelay * 2); + } + if (repeatCount > 2) { + handler.postDelayed(new Runnable() { + public void run() { + // Actions to do after 5 seconds + if (isRinging) { + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); + } + } + }, repeatDelay * 3); + } + if (repeatCount > 3) { + handler.postDelayed(new Runnable() { + public void run() { + // Actions to do after 5 seconds + if (isRinging) { + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); + } + } + }, repeatDelay * 4); + } + if (repeatCount > 4) { + handler.postDelayed(new Runnable() { + public void run() { + // Actions to do after 5 seconds + if (isRinging) { + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); + } + } + }, repeatDelay * 5); + } break; case CallSpec.CALL_START: - WatchXPlusDeviceCoordinator.getRepeat = 1; - sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "stop", false); + isRinging = false; + cancelNotification(); + break; + case CallSpec.CALL_REJECT: + isRinging = false; + cancelNotification(); + break; + case CallSpec.CALL_ACCEPT: + isRinging = false; + cancelNotification(); + break; + case CallSpec.CALL_OUTGOING: + isRinging = false; + cancelNotification(); break; case CallSpec.CALL_END: - WatchXPlusDeviceCoordinator.getRepeat = 0; - sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "stop", false); + if (isRinging) { + // it's a missed call + // don't clear notification to preserve small icon near bluetooth + isRinging = false; + } else { + isRinging = false; + cancelNotification(); + } break; default: break; @@ -573,7 +649,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onHeartRateTest() { - + //requestHeartRateMeasurement(); } @Override @@ -658,7 +734,29 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { requestBloodPressureMeasurement(); } +// check status of blood pressure calibration + private WatchXPlusDeviceSupport getBloodPressureCalibrationStatus(TransactionBuilder builder) { + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_IS_BP_CALIBRATED, + WatchXPlusConstants.READ_VALUE)); + + return this; + } + + private void handleBloodPressureCalibrationStatus(byte[] value) { + if (Conversion.calculateHigh(value[8]) != 0) { + WatchXPlusDeviceCoordinator.isBPCalibrated = false; + } else { + WatchXPlusDeviceCoordinator.isBPCalibrated = true; + } + } +// end check status of blood pressure calibration + private void requestBloodPressureMeasurement() { + if (!WatchXPlusDeviceCoordinator.isBPCalibrated) { + LOG.warn("BP is NOT calibrated"); + return; + } try { TransactionBuilder builder = performInitialized("bpMeasure"); @@ -673,6 +771,29 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } + + /* + // not working!!! + private void requestHeartRateMeasurement() { + try { + TransactionBuilder builder = performInitialized("hrMeasure"); + + byte[] command = new byte[]{0x05, 0x0B}; + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.READ_VALUE)); + + // builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + // buildCommand(command, + // WatchXPlusConstants.TASK, new byte[]{0x01})); + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("Unable to request HR Measure", e); + } + } +*/ + @Override public void onSendWeather(WeatherSpec weatherSpec) { try { @@ -708,18 +829,19 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_FIRMWARE_INFO, 5)) { handleFirmwareInfo(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_SHAKE_SWITCH, 5)) { - LOG.info(" RESP LIGHT SCREEN "); handleShakeState(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DISCONNECT_REMIND, 5)) { - LOG.info(" RESP DISCONNECT REMINDER "); handleDisconnectReminderState(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BATTERY_INFO, 5)) { handleBatteryState(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_TIME_SETTINGS, 5)) { handleTime(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_IS_BP_CALIBRATED, 5)) { + handleBloodPressureCalibrationStatus(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BUTTON_INDICATOR, 5)) { this.onReverseFindDevice(true); // It looks like WatchXPlus doesn't send this action +// WRONG: WatchXPlus send this on find phone LOG.info(" Unhandled action: Button pressed"); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_ALARM_INDICATOR, 5)) { LOG.info(" Alarm active: id=" + value[8]); @@ -1409,5 +1531,25 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { (byte) (value >> 8), (byte) value}; } + + static int calculateLow(byte... bArr) { + int i = 0; + int i2 = 0; + while (i < bArr.length) { + i2 += (bArr[i] & 255) << (i * 8); + i++; + } + return i2; + } + + static int calculateHigh(byte... bArr) { + int i = 0; + int i2 = 0; + while (i < bArr.length) { + i2 += (bArr[i] & 255) << (((bArr.length - 1) - i) * 8); + i++; + } + return i2; + } } } diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index e3aab489e..10ac20d9b 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -284,6 +284,13 @@ Watch 9 свързване Watch 9 сверяване WatchX Plus сверяване + Edinici + Формат на часа + Калибриране на височина + Повтори известия за звънене + Възможни стойности min=0, max=5 + WatchXPlus настройки + WatchXPlus калибриране Наблюдение/анализ на съня Съхраняване на log файлове Инициализиране diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ea014997e..e3409ae9d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -190,8 +190,10 @@ Units Time format Altitude calibration - Repeat vibration on call + Repeat call notification + Possible values min=0, max=5 WatchXPlus settings + WatchXPlus calibration Makibes HR3 settings diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 5d1cd46e9..3bf87b1a8 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -583,19 +583,24 @@ android:key="pref_category_watchxplus_general" android:title="@string/pref_header_general"> + + + + - - - +