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 dfe3a641e..bc25a933e 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,7 +36,12 @@ 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_CONTINIOUS = "watchxplus_continious"; + public static final String PREF_MISSED_CALL = "watchxplus_missed"; public static final String PREF_IS_BP_CALIBRATED = "watchxplus_is_bp_calibrated"; + public static final String PREF_BUTTON_REJECT = "watchxplus_button_reject"; + public static final String PREF_SHAKE_REJECT = "watchxplus_shake_reject"; + // time format constants public static final byte ARG_SET_TIMEMODE_24H = 0x00; @@ -71,6 +76,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_BUTTON_WHILE_RING = new byte[]{0x04, 0x03, 0x03}; 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 71863c7e8..6814968fd 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 @@ -223,6 +223,14 @@ Prefs from device settings on main page } } + 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; + } + } /* Values from device specific settings page */ @@ -236,17 +244,30 @@ Values from device specific settings page return (int) prefs.getInt(WatchXPlusConstants.PREF_REPEAT, 1); } +//read continious call notification + public static boolean getContiniousVibrationOnCall(String address) { + return (boolean) prefs.getBoolean(WatchXPlusConstants.PREF_CONTINIOUS, false); + } + +//read missed call notification + public static boolean getMissedCallReminder(String address) { + return (boolean) prefs.getBoolean(WatchXPlusConstants.PREF_MISSED_CALL, false); + } + +//read button reject call settings + public static boolean getButtonReject(String address) { + return (boolean) prefs.getBoolean(WatchXPlusConstants.PREF_BUTTON_REJECT, false); + } + +//read shake wrist reject call settings + public static boolean getShakeReject(String address) { + return (boolean) prefs.getBoolean(WatchXPlusConstants.PREF_SHAKE_REJECT, false); + } + /* 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 331f7bc99..14e100b3e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -69,6 +69,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntry; import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntryDao; import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; @@ -249,8 +250,8 @@ public class NotificationListener extends NotificationListenerService { } if (shouldIgnore(sbn)) { - if (!"com.sec.android.app.clockpackage".equals(sbn.getPackageName())) { // allow phone alarm notification - LOG.info("Ignore notification: " + sbn.getPackageName()); + if (!"com.sec.android.app.clockpackage".equals(sbn.getPackageName())) { // workaround to allow phone alarm notification + LOG.info("Ignore notification: " + sbn.getPackageName()); // need to fix return; } } @@ -677,7 +678,6 @@ public class NotificationListener extends NotificationListenerService { if (!isServiceRunning() || sbn == null) { return true; } - return shouldIgnoreSource(sbn.getPackageName()) || shouldIgnoreNotification( sbn.getNotification(), sbn.getPackageName()); @@ -721,8 +721,9 @@ public class NotificationListener extends NotificationListenerService { MediaSessionCompat.Token mediaSession = getMediaSession(notification); //try to handle media session notifications - if (mediaSession != null && handleMediaSessionNotification(mediaSession)) + if (mediaSession != null && handleMediaSessionNotification(mediaSession)) { return true; + } NotificationType type = AppNotificationType.getInstance().get(source); //ignore notifications marked as LocalOnly https://developer.android.com/reference/android/app/Notification.html#FLAG_LOCAL_ONLY @@ -743,7 +744,6 @@ public class NotificationListener extends NotificationListenerService { return true; } } - return (notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT; } 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 a0bdf88fb..fe5d55256 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 @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.Uri; +import android.os.CountDownTimer; import android.os.Handler; import androidx.annotation.IntRange; @@ -53,6 +54,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.DataType; import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants; @@ -62,6 +64,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusHealthActivityOverlay; import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusHealthActivityOverlayDao; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; +import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; @@ -81,13 +84,13 @@ 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; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; + public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { private boolean needsAuth; @@ -466,80 +469,48 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { alarmValue)); } } - int repeat = 0; + + // variables to handle ring notifications boolean isRinging = false; + int remainingRepeats = 0; @Override public void onSetCallState(final CallSpec callSpec) { - int repeatDelay = 5000; + final int repeatDelay = 5000; // repeat delay of 5 sec + // get settings from device settings page + final boolean continiousRing = WatchXPlusDeviceCoordinator.getContiniousVibrationOnCall(getDevice().getAddress()); + boolean missedCall = WatchXPlusDeviceCoordinator.getMissedCallReminder(getDevice().getAddress()); 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; - } + // check if repeatCount is in boundaries min=0, max=10 + if (repeatCount < 0) repeatCount = 0; + if (repeatCount > 10) repeatCount = 10; // limit repeats to 10 switch (callSpec.command) { case CallSpec.CALL_INCOMING: 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) { + remainingRepeats = repeatCount; + if (("Phone".equals(callSpec.name)) || (callSpec.name.contains("ropusn")) || (callSpec.name.contains("issed"))) { + // do nothing for notifications without caller name, e.g. system call event + } else { + // send first notification + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); + // init repeat handler + final Handler handler = new Handler(); handler.postDelayed(new Runnable() { public void run() { - // Actions to do after 5 seconds - if (isRinging) { + // Actions to do after repeatDelay seconds + if (((isRinging) && (remainingRepeats > 0)) || ((isRinging) && (continiousRing))) { + remainingRepeats = remainingRepeats - 1; sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); + // re-run handler + handler.postDelayed(this, repeatDelay); + } else { + remainingRepeats = 0; + // stop handler + handler.removeCallbacks(this); } } }, 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: isRinging = false; @@ -559,19 +530,44 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { break; case CallSpec.CALL_END: if (isRinging) { - // it's a missed call - // don't clear notification to preserve small icon near bluetooth + // it's a missed call, don't clear notification to preserve small icon near bluetooth isRinging = false; + // send missed call notification if enabled in settings + if (missedCall) { + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "Missed call"); + } } else { isRinging = false; cancelNotification(); } break; default: + isRinging = false; + cancelNotification(); break; } } +// handle button press while ringing + private void handleButtonWhenRing(byte[] value) { + GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl(); + // get saved settings if true - reject call, otherwise ignore call + boolean buttonReject = WatchXPlusDeviceCoordinator.getButtonReject(getDevice().getAddress()); + if (buttonReject) { + LOG.info(" call rejected "); + isRinging = false; + callCmd.event = GBDeviceEventCallControl.Event.REJECT; + evaluateGBDeviceEvent(callCmd); + cancelNotification(); + } else { + LOG.info(" call ignored "); + isRinging = false; + callCmd.event = GBDeviceEventCallControl.Event.IGNORE; + evaluateGBDeviceEvent(callCmd); + cancelNotification(); + } + } + @Override public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { @@ -830,6 +826,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { handleFirmwareInfo(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_SHAKE_SWITCH, 5)) { handleShakeState(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BUTTON_WHILE_RING, 5)) { + handleButtonWhenRing(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DISCONNECT_REMIND, 5)) { handleDisconnectReminderState(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BATTERY_INFO, 5)) { @@ -1387,9 +1385,12 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { WatchXPlusDeviceCoordinator.shouldEnableHeadsUpScreen(sharedPreferences)); } - // Command to toggle Lift Wrist to Light Screen + // Command to toggle Lift Wrist to Light Screen, and shake to ignore/reject call private WatchXPlusDeviceSupport setHeadsUpScreen(TransactionBuilder transactionBuilder, boolean enable) { - byte refuseCall = 0x00; // force refuse call to OFF + boolean shakeReject = WatchXPlusDeviceCoordinator.getShakeReject(getDevice().getAddress()); + byte refuseCall = 0x00; // force shake wrist to ignore/reject call to OFF + // returned characteristic is equal with button press while ringing + if (shakeReject) refuseCall = 0x01; byte lightScreen = 0x00; if (enable) { lightScreen = 0x01; @@ -1427,7 +1428,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { WatchXPlusConstants.READ_VALUE)); return this; } -// Request status of Lift Wrist to Light Screen, and Shake to Refuse Call +// Request status of Lift Wrist to Light Screen, and Shake to Ignore/Reject Call public WatchXPlusDeviceSupport getShakeStatus(TransactionBuilder transactionBuilder) { transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_SHAKE_SWITCH, diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 10ac20d9b..3b7f192df 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -288,7 +288,13 @@ Формат на часа Калибриране на височина Повтори известия за звънене - Възможни стойности min=0, max=5 + Възможни стойности min=0, max=10 + Известие докато звъни + Изкл. - игнорира, Вкл. - отказ + Бутона игнорира/отказва повикване + Дублира действието на бутона + Разклащането игнорира/отказва повикване + Известие за пропуснато повикване WatchXPlus настройки WatchXPlus калибриране Наблюдение/анализ на съня diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e3409ae9d..fe73c3cd3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -191,8 +191,14 @@ Time format Altitude calibration Repeat call notification - Possible values min=0, max=5 + Possible values min=0, max=10 + Vibration during phone ring + Vibration on missed call WatchXPlus settings + Off - ignore, On - reject + Button ignore/reject call + Duplicates watch button action + Shake wrist ignore/reject call WatchXPlus calibration Makibes HR3 settings diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 3bf87b1a8..a44d6e6e5 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -588,6 +588,28 @@ android:key="watchxplus_repeat" android:summary="@string/pref_wxp_title_repeat_on_call_summary" android:title="@string/pref_wxp_title_repeat_on_call"/> + + + +