1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-29 13:26:50 +01:00
- Fixed setting for repeat watch vibration during phone ring. (code need to be cleared and optimised)
- On my samsung phone, when call is ended, on watch shows notification with name "Phone" and vibrates until disconnect from app. I tryed to fix it, but don't know is it successful.
ADD:
- Add command for cancel notification, e.g. when answer phone call.
- Add blood pressure calibration status.
NEED MORE TESTING:
- On watch preserve small phone icon near bluetooth icon only when call is missed, in other cases clear it.
- When phone alarm triger - send notification on watch

P.S. To apply altitude calibration, need to disconnect and reconnect watch
This commit is contained in:
mamutcho 2019-11-06 00:14:29 +02:00
parent 54f4bbeeef
commit 81997e7725
7 changed files with 264 additions and 78 deletions

View File

@ -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};

View File

@ -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;
}
}
}

View File

@ -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()) {

View File

@ -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;
}
}
}

View File

@ -284,6 +284,13 @@
<string name="title_activity_watch9_pairing">Watch 9 свързване</string>
<string name="title_activity_watch9_calibration">Watch 9 сверяване</string>
<string name="title_activity_watchXplus_calibration">WatchX Plus сверяване</string>
<string name="pref_wxp_title_unit_system">Edinici</string>
<string name="pref_wxp_title_timeformat">Формат на часа</string>
<string name="pref_wxp_title_altitude">Калибриране на височина</string>
<string name="pref_wxp_title_repeat_on_call">Повтори известия за звънене</string>
<string name="pref_wxp_title_repeat_on_call_summary">Възможни стойности min=0, max=5</string>
<string name="preferences_watchxplus_settings">WatchXPlus настройки</string>
<string name="pref_header_wxp_calibration">WatchXPlus калибриране</string>
<string name="title_activity_sleepmonitor">Наблюдение/анализ на съня</string>
<string name="pref_write_logfiles">Съхраняване на log файлове</string>
<string name="initializing">Инициализиране</string>

View File

@ -190,8 +190,10 @@
<string name="pref_wxp_title_unit_system">Units</string>
<string name="pref_wxp_title_timeformat">Time format</string>
<string name="pref_wxp_title_altitude">Altitude calibration</string>
<string name="pref_wxp_title_repeat_on_call">Repeat vibration on call</string>
<string name="pref_wxp_title_repeat_on_call">Repeat call notification</string>
<string name="pref_wxp_title_repeat_on_call_summary">Possible values min=0, max=5</string>
<string name="preferences_watchxplus_settings">WatchXPlus settings</string>
<string name="pref_header_wxp_calibration">WatchXPlus calibration</string>
<!-- Makibes HR3 Preferences -->
<string name="preferences_makibes_hr3_settings">Makibes HR3 settings</string>
<!-- ID115 Preferences -->

View File

@ -583,19 +583,24 @@
android:key="pref_category_watchxplus_general"
android:title="@string/pref_header_general">
<EditTextPreference
android:defaultValue="0"
android:key="watchxplus_repeat"
android:summary="@string/pref_wxp_title_repeat_on_call_summary"
android:title="@string/pref_wxp_title_repeat_on_call"/>
</PreferenceCategory>
<PreferenceCategory
android:key="pref_category_watchxplus_calibration"
android:title="@string/pref_header_wxp_calibration">
<EditTextPreference
android:defaultValue="200"
android:key="watchxplus_altitude"
android:title="@string/pref_wxp_title_altitude"/>
<EditTextPreference
android:defaultValue="1"
android:key="watchxplus_repeat"
android:title="@string/pref_wxp_title_repeat_on_call"/>
</PreferenceCategory>
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory