From 76427a384858dbde0897afa564a57a32fba62d1a Mon Sep 17 00:00:00 2001 From: ksiwczynski Date: Thu, 14 Mar 2019 03:58:57 +0100 Subject: [PATCH 001/546] Watch X Plus discovery --- .../WatchXPlusDeviceCoordinator.java | 154 ++++++++++++++++++ .../gadgetbridge/model/DeviceType.java | 2 + .../gadgetbridge/util/DeviceHelper.java | 2 + app/src/main/res/values/strings.xml | 2 + 4 files changed, 160 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java new file mode 100644 index 000000000..8cdb41daf --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java @@ -0,0 +1,154 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.watchxplus; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.bluetooth.le.ScanFilter; +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import android.os.ParcelUuid; + +import java.util.Collection; +import java.util.Collections; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9Constants; +import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9PairingActivity; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; + +public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { + + + @NonNull + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public Collection createBLEScanFilters() { + // TODO constants for watch x plus + //ParcelUuid watchXpService = new ParcelUuid(Watch9Constants.UUID_SERVICE_WATCH9); + ScanFilter filter = new ScanFilter.Builder().setDeviceName("Watch XPlus").build(); + return Collections.singletonList(filter); + } + + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + + } + + @Override + public int getBondingStyle(GBDevice device) { + return BONDING_STYLE_NONE; + } + + @NonNull + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + // TODO constants and mac for watch x plus + String macAddress = candidate.getMacAddress().toUpperCase(); + String deviceName = candidate.getName().toUpperCase(); + if (candidate.supportsService(Watch9Constants.UUID_SERVICE_WATCH9)) { + return DeviceType.WATCHXPLUS; + } else if (macAddress.startsWith("1C:87:79")) { + return DeviceType.WATCHXPLUS; + } else if (deviceName.equals("WATCH XPLUS")) { + return DeviceType.WATCHXPLUS; + } + return DeviceType.UNKNOWN; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.WATCHXPLUS; + } + + @Nullable + @Override + public Class getPairingActivity() { + // TODO watch X plus! + return Watch9PairingActivity.class; + } + + @Override + public boolean supportsActivityDataFetching() { + return false; + } + + @Override + public boolean supportsActivityTracking() { + return false; + } + + @Override + public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { + return null; + } + + @Override + public InstallHandler findInstallHandler(Uri uri, Context context) { + return null; + } + + @Override + public boolean supportsScreenshots() { + return false; + } + + @Override + public int getAlarmSlotCount() { + return 0; + } + + @Override + public boolean supportsSmartWakeup(GBDevice device) { + return false; + } + + @Override + public boolean supportsHeartRateMeasurement(GBDevice device) { + return false; + } + + @Override + public String getManufacturer() { + return "Lenovo"; + } + + @Override + public boolean supportsAppsManagement() { + return false; + } + + @Override + public Class getAppsManagementActivity() { + return null; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return false; + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index 9b7badf23..0a631c4bb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -49,6 +49,8 @@ public enum DeviceType { ZETIME(80, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_mykronoz_zetime), ID115(90, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_id115), WATCH9(100, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_watch9), + WATCHX(101, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_watchx), + WATCHXPLUS(102, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_watchxplus), ROIDMI(110, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi), ROIDMI3(112, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi3), CASIOGB6900(120, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_casiogb6900), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 02121a3f4..3b146638c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -62,6 +62,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator; @@ -217,6 +218,7 @@ public class DeviceHelper { result.add(new ZeTimeCoordinator()); result.add(new ID115Coordinator()); result.add(new Watch9DeviceCoordinator()); + result.add(new WatchXPlusDeviceCoordinator()); result.add(new Roidmi1Coordinator()); result.add(new Roidmi3Coordinator()); result.add(new CasioGB6900DeviceCoordinator()); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index caf9172ce..2328377eb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -619,6 +619,8 @@ MyKronoz ZeTime ID115 Watch 9 + Watch X + Watch X Plus Roidmi Roidmi 3 Casio GB-6900 From 184d2a94093cddb125fe808e443a4a11dc835408 Mon Sep 17 00:00:00 2001 From: ksiwczynski Date: Thu, 14 Mar 2019 04:04:57 +0100 Subject: [PATCH 002/546] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5f239db40..47d70173c 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ vendor's servers. * HPlus Devices (e.g. ZeBand) [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/HPlus) * ID115 (WIP) * Lenovo Watch 9 (WIP) +* Lenovo Watch X Plus (WIP) * Liveview (WIP) * Mi Band, Mi Band 1A, Mi Band 1S [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band) * Mi Band 2 [Wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-2) From 7c2f729ac5e54202117006ee15c613ec8309488d Mon Sep 17 00:00:00 2001 From: ksiwczynski Date: Thu, 18 Apr 2019 19:44:59 +0200 Subject: [PATCH 003/546] gradle update --- app/build.gradle | 8 ++++---- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1a9e76995..c877660e9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -132,10 +132,10 @@ task pmd(type: Pmd) { xml.enabled = false html.enabled = true xml { - destination "$project.buildDir/reports/pmd/pmd.xml" + destination file("$project.buildDir/reports/pmd/pmd.xml") } html { - destination "$project.buildDir/reports/pmd/pmd.html" + destination file("$project.buildDir/reports/pmd/pmd.html") } } } @@ -152,10 +152,10 @@ task findbugs(type: FindBugs) { xml.enabled = false html.enabled = true xml { - destination "$project.buildDir/reports/findbugs/findbugs-output.xml" + destination file("$project.buildDir/reports/findbugs/findbugs-output.xml") } html { - destination "$project.buildDir/reports/findbugs/findbugs-output.html" + destination file("$project.buildDir/reports/findbugs/findbugs-output.html") } } } diff --git a/build.gradle b/build.gradle index 1c9c5b9f5..ef1b8035a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:3.4.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5c2db3913..9ac47aa1c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Jan 20 22:13:05 EET 2018 +#Thu Apr 18 19:22:52 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip From cf61ab9d3821a3003314570631a466a0a510a3f7 Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Fri, 18 Oct 2019 00:02:53 +0200 Subject: [PATCH 004/546] Fixed battery level parsing from response Added basic notification support --- app/src/main/AndroidManifest.xml | 6 + .../WatchXPlusCalibrationActivity.java | 123 ++++ .../watchxplus/WatchXPlusConstants.java | 93 +++ .../WatchXPlusDeviceCoordinator.java | 21 +- .../watchxplus/WatchXPlusPairingActivity.java | 129 ++++ .../service/DeviceSupportFactory.java | 4 + .../watchxplus/WatchXPlusDeviceSupport.java | 649 ++++++++++++++++++ .../watchxplus/operations/InitOperation.java | 94 +++ 8 files changed, 1107 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusCalibrationActivity.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusPairingActivity.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/operations/InitOperation.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eb7b6d8de..1d5f5afc4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -406,6 +406,12 @@ + + . */ +package nodomain.freeyourgadget.gadgetbridge.devices.watchxplus; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.widget.Button; +import android.widget.NumberPicker; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class WatchXPlusCalibrationActivity extends AbstractGBActivity { + + private static final String STATE_DEVICE = "stateDevice"; + GBDevice device; + + NumberPicker pickerHour, pickerMinute, pickerSecond; + + Handler handler; + Runnable holdCalibration; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_watch9_calibration); + + pickerHour = findViewById(R.id.np_hour); + pickerMinute = findViewById(R.id.np_minute); + pickerSecond = findViewById(R.id.np_second); + + pickerHour.setMinValue(1); + pickerHour.setMaxValue(12); + pickerHour.setValue(12); + pickerMinute.setMinValue(0); + pickerMinute.setMaxValue(59); + pickerMinute.setValue(0); + pickerSecond.setMinValue(0); + pickerSecond.setMaxValue(59); + pickerSecond.setValue(0); + + handler = new Handler(); + holdCalibration = new Runnable() { + @Override + public void run() { + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(WatchXPlusConstants.ACTION_CALIBRATION_HOLD)); + handler.postDelayed(this, 10000); + } + }; + + Intent intent = getIntent(); + device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); + if (device == null && savedInstanceState != null) { + device = savedInstanceState.getParcelable(STATE_DEVICE); + } + if (device == null) { + finish(); + } + + final Button btCalibrate = findViewById(R.id.watch9_bt_calibrate); + btCalibrate.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + btCalibrate.setEnabled(false); + handler.removeCallbacks(holdCalibration); + Intent calibrationData = new Intent(WatchXPlusConstants.ACTION_CALIBRATION_SEND); + calibrationData.putExtra(WatchXPlusConstants.VALUE_CALIBRATION_HOUR, pickerHour.getValue()); + calibrationData.putExtra(WatchXPlusConstants.VALUE_CALIBRATION_MINUTE, pickerMinute.getValue()); + calibrationData.putExtra(WatchXPlusConstants.VALUE_CALIBRATION_SECOND, pickerSecond.getValue()); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibrationData); + finish(); + } + }); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(STATE_DEVICE, device); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + device = savedInstanceState.getParcelable(STATE_DEVICE); + } + + @Override + protected void onStart() { + super.onStart(); + Intent calibration = new Intent(WatchXPlusConstants.ACTION_CALIBRATION); + calibration.putExtra(WatchXPlusConstants.ACTION_ENABLE, true); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration); + handler.postDelayed(holdCalibration, 1000); + } + + @Override + protected void onStop() { + super.onStop(); + Intent calibration = new Intent(WatchXPlusConstants.ACTION_CALIBRATION); + calibration.putExtra(WatchXPlusConstants.ACTION_ENABLE, false); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration); + handler.removeCallbacks(holdCalibration); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java new file mode 100644 index 000000000..143d0d5eb --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java @@ -0,0 +1,93 @@ +/* Copyright (C) 2018-2019 maxirnilian + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.watchxplus; + +import java.util.UUID; + +public final class WatchXPlusConstants { + public static final UUID UUID_SERVICE_WATCHXPLUS = UUID.fromString("0000a800-0000-1000-8000-00805f9b34fb"); + + public static final UUID UUID_UNKNOWN_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + + public static final UUID UUID_CHARACTERISTIC_WRITE = UUID.fromString("0000a801-0000-1000-8000-00805f9b34fb"); + public static final UUID UUID_CHARACTERISTIC_UNKNOWN_2 = UUID.fromString("0000a802-0000-1000-8000-00805f9b34fb"); + public static final UUID UUID_CHARACTERISTIC_UNKNOWN_3 = UUID.fromString("0000a803-0000-1000-8000-00805f9b34fb"); + public static final UUID UUID_CHARACTERISTIC_UNKNOWN_4 = UUID.fromString("0000a804-0000-1000-8000-00805f9b34fb"); + + public static final int NOTIFICATION_CHANNEL_DEFAULT = 7; + public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 1024; + + public static final byte RESPONSE = 0x13; + public static final byte REQUEST = 0x31; + + public static final byte WRITE_VALUE = 0x01; + public static final byte READ_VALUE = 0x02; + public static final byte TASK = 0x04; + public static final byte KEEP_ALIVE = -0x80; + + public static final byte[] CMD_HEADER = new byte[]{0x23, 0x01, 0x00, 0x00, 0x00}; + + // byte[] COMMAND = new byte[]{0x23, 0x01, 0x00, 0x31, 0x00, ... , 0x00} + // | | | | | | └ Checksum + // | | | | | └ Command + value + // | | | | └ Sequence number + // | | | └ Response/Request indicator + // | | └ Value length + // | | + // └-----└ Header + + public static final byte[] CMD_FIRMWARE_INFO = new byte[]{0x01, 0x02}; + public static final byte[] CMD_AUTHORIZATION_TASK = new byte[]{0x01, 0x05}; + public static final byte[] CMD_TIME_SETTINGS = new byte[]{0x01, 0x08}; + public static final byte[] CMD_ALARM_SETTINGS = new byte[]{0x01, 0x0A}; + public static final byte[] CMD_BATTERY_INFO = new byte[]{0x01, 0x14}; + + public static final byte[] CMD_NOTIFICATION_TASK = new byte[]{0x03, 0x01}; + public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06}; + public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02}; + public static final byte[] CMD_CALIBRATION_INIT_TASK = new byte[]{0x03, 0x31}; + public static final byte[] CMD_CALIBRATION_TASK = new byte[]{0x03, 0x33, 0x01}; + public static final byte[] CMD_CALIBRATION_KEEP_ALIVE = new byte[]{0x03, 0x34}; + public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61}; + + public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02}; + + public static final byte[] RESP_AUTHORIZATION_TASK = new byte[]{0x01, 0x01, 0x05}; + public static final byte[] RESP_BUTTON_INDICATOR = new byte[]{0x04, 0x03, 0x11}; + public static final byte[] RESP_ALARM_INDICATOR = new byte[]{-0x80, 0x01, 0x0A}; + + public static final byte[] RESP_FIRMWARE_INFO = new byte[]{0x08, 0x01, 0x02}; + public static final byte[] RESP_TIME_SETTINGS = new byte[]{0x08, 0x01, 0x08}; + public static final byte[] RESP_BATTERY_INFO = new byte[]{0x08, 0x01, 0x14}; + public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x08, 0x03, 0x02}; + + public static final String ACTION_ENABLE = "action.watch9.enable"; + + public static final String ACTION_CALIBRATION + = "nodomain.freeyourgadget.gadgetbridge.devices.action.watchxplus.start_calibration"; + public static final String ACTION_CALIBRATION_SEND + = "nodomain.freeyourgadget.gadgetbridge.devices.action.watchxplus.send_calibration"; + public static final String ACTION_CALIBRATION_HOLD + = "nodomain.freeyourgadget.gadgetbridge.devices.action.watchxplus.keep_calibrating"; + public static final String VALUE_CALIBRATION_HOUR + = "value.watch9.calibration_hour"; + public static final String VALUE_CALIBRATION_MINUTE + = "value.watch9.calibration_minute"; + public static final String VALUE_CALIBRATION_SECOND + = "value.watch9.calibration_second"; + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java index 8cdb41daf..a85ba3882 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java @@ -17,8 +17,8 @@ import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; -import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9Constants; -import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9PairingActivity; +import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusPairingActivity; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -33,9 +33,8 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) public Collection createBLEScanFilters() { - // TODO constants for watch x plus - //ParcelUuid watchXpService = new ParcelUuid(Watch9Constants.UUID_SERVICE_WATCH9); - ScanFilter filter = new ScanFilter.Builder().setDeviceName("Watch XPlus").build(); + ParcelUuid watchXpService = new ParcelUuid(WatchXPlusConstants.UUID_SERVICE_WATCHXPLUS); + ScanFilter filter = new ScanFilter.Builder().setServiceUuid(watchXpService).build(); return Collections.singletonList(filter); } @@ -45,21 +44,20 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { } @Override - public int getBondingStyle(GBDevice device) { + public int getBondingStyle() { return BONDING_STYLE_NONE; } @NonNull @Override public DeviceType getSupportedType(GBDeviceCandidate candidate) { - // TODO constants and mac for watch x plus String macAddress = candidate.getMacAddress().toUpperCase(); String deviceName = candidate.getName().toUpperCase(); - if (candidate.supportsService(Watch9Constants.UUID_SERVICE_WATCH9)) { + if (candidate.supportsService(WatchXPlusConstants.UUID_SERVICE_WATCHXPLUS)) { return DeviceType.WATCHXPLUS; - } else if (macAddress.startsWith("1C:87:79")) { + } else if (macAddress.startsWith("DC:41:E5")) { return DeviceType.WATCHXPLUS; - } else if (deviceName.equals("WATCH XPLUS")) { + } else if (deviceName.equalsIgnoreCase("WATCH XPLUS")) { return DeviceType.WATCHXPLUS; } return DeviceType.UNKNOWN; @@ -73,8 +71,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Nullable @Override public Class getPairingActivity() { - // TODO watch X plus! - return Watch9PairingActivity.class; + return WatchXPlusPairingActivity.class; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusPairingActivity.java new file mode 100644 index 000000000..79b209c51 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusPairingActivity.java @@ -0,0 +1,129 @@ +/* Copyright (C) 2018-2019 Daniele Gobbetti, maxirnilian + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.watchxplus; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.widget.TextView; +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; +import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; +import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class WatchXPlusPairingActivity extends AbstractGBActivity { + private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusPairingActivity.class); + + private static final String STATE_DEVICE_CANDIDATE = "stateDeviceCandidate"; + + private TextView message; + private GBDeviceCandidate deviceCandidate; + + private final BroadcastReceiver mPairingReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (GBDevice.ACTION_DEVICE_CHANGED.equals(intent.getAction())) { + GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); + LOG.debug("pairing activity: device changed: " + device); + if (deviceCandidate.getMacAddress().equals(device.getAddress())) { + if (device.isInitialized()) { + pairingFinished(); + } else if (device.isConnecting() || device.isInitializing()) { + LOG.info("still connecting/initializing device..."); + } + } + } + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_watch9_pairing); + + message = findViewById(R.id.watch9_pair_message); + Intent intent = getIntent(); + deviceCandidate = intent.getParcelableExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE); + if (deviceCandidate == null && savedInstanceState != null) { + deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE); + } + if (deviceCandidate == null) { + Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show(); + startActivity(new Intent(this, DiscoveryActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); + finish(); + return; + } + startPairing(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(STATE_DEVICE_CANDIDATE, deviceCandidate); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE); + } + + @Override + protected void onDestroy() { + AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), mPairingReceiver); + super.onDestroy(); + } + + private void startPairing() { + message.setText(getString(R.string.pairing, deviceCandidate)); + + IntentFilter filter = new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED); + LocalBroadcastManager.getInstance(this).registerReceiver(mPairingReceiver, filter); + + GBApplication.deviceService().disconnect(); + GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate); + if (device != null) { + GBApplication.deviceService().connect(device, true); + } else { + GB.toast(this, "Unable to connect, can't recognize the device type: " + deviceCandidate, Toast.LENGTH_LONG, GB.ERROR); + } + } + + private void pairingFinished() { + AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), mPairingReceiver); + + Intent intent = new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + + finish(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java index ce3f7a039..dba93c3aa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -52,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.watchxplus.WatchXPlusDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.zetime.ZeTimeDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -183,6 +184,9 @@ public class DeviceSupportFactory { case WATCH9: deviceSupport = new ServiceDeviceSupport(new Watch9DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; + case WATCHXPLUS: + deviceSupport = new ServiceDeviceSupport(new WatchXPlusDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING)); + break; case ROIDMI: deviceSupport = new ServiceDeviceSupport(new RoidmiSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java new file mode 100644 index 000000000..0d4c4869d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java @@ -0,0 +1,649 @@ +/* Copyright (C) 2018-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, maxirnilian, Sebastian Kranz + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.watchxplus; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; + +import androidx.annotation.IntRange; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; +import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusConstants; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +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.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.devices.watchxplus.operations.InitOperation; +import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils; +import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils; +import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; + +public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { + + private boolean needsAuth; + private int sequenceNumber = 0; + private boolean isCalibrationActive = false; + + private byte ACK_CALIBRATION = 0; + + private final GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo(); + private final GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo(); + + private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusDeviceSupport.class); + + private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String broadcastAction = intent.getAction(); + switch (broadcastAction) { + case WatchXPlusConstants.ACTION_CALIBRATION: + enableCalibration(intent.getBooleanExtra(WatchXPlusConstants.ACTION_ENABLE, false)); + break; + case WatchXPlusConstants.ACTION_CALIBRATION_SEND: + int hour = intent.getIntExtra(WatchXPlusConstants.VALUE_CALIBRATION_HOUR, -1); + int minute = intent.getIntExtra(WatchXPlusConstants.VALUE_CALIBRATION_MINUTE, -1); + int second = intent.getIntExtra(WatchXPlusConstants.VALUE_CALIBRATION_SECOND, -1); + if (hour != -1 && minute != -1 && second != -1) { + sendCalibrationData(hour, minute, second); + } + break; + case WatchXPlusConstants.ACTION_CALIBRATION_HOLD: + holdCalibration(); + break; + } + } + }; + + public WatchXPlusDeviceSupport() { + super(LOG); + addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS); + addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE); + addSupportedService(WatchXPlusConstants.UUID_SERVICE_WATCHXPLUS); + + LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(WatchXPlusConstants.ACTION_CALIBRATION); + intentFilter.addAction(WatchXPlusConstants.ACTION_CALIBRATION_SEND); + intentFilter.addAction(WatchXPlusConstants.ACTION_CALIBRATION_HOLD); + broadcastManager.registerReceiver(broadcastReceiver, intentFilter); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + try { + boolean auth = needsAuth; + needsAuth = false; + new InitOperation(auth, this, builder).perform(); + } catch (IOException e) { + e.printStackTrace(); + } + + return builder; + } + + @Override + public boolean connectFirstTime() { + needsAuth = true; + return super.connect(); + } + + @Override + public boolean useAutoConnect() { + return true; + } + + @Override + public void onNotification(NotificationSpec notificationSpec) { + + String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title); + + String message = StringUtils.truncate(senderOrTitle, 32) + "\0"; +// TODO: Commented out to simplify testing +// if (notificationSpec.subject != null) { +// message += StringUtils.truncate(notificationSpec.subject, 128) + "\n\n"; +// } +// if (notificationSpec.body != null) { +// message += StringUtils.truncate(notificationSpec.body, 128); +// } + + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_DEFAULT, message); + } + + private void sendNotification(int notificationChannel, String notificationText) { + try { + TransactionBuilder builder = performInitialized("showNotification"); + byte[] command = WatchXPlusConstants.CMD_NOTIFICATION_TEXT_TASK; + byte[] text = notificationText.getBytes("UTF-8"); + byte[] value = new byte[text.length + 2]; + value[0] = (byte)(notificationChannel); +// TODO: Split message into 9-byte arrays and send them one by one. +// Set the message index to FF to indicate end of message + value[1] = (byte) 0xFF; + System.arraycopy(text, 0, value, 2, text.length); + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.KEEP_ALIVE, + value)); + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to send notification", e); + } + } + + private WatchXPlusDeviceSupport enableNotificationChannels(TransactionBuilder builder) { + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_NOTIFICATION_SETTINGS, + WatchXPlusConstants.WRITE_VALUE, + new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF})); + + return this; + } + + public WatchXPlusDeviceSupport authorizationRequest(TransactionBuilder builder, boolean firstConnect) { + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_AUTHORIZATION_TASK, + WatchXPlusConstants.TASK, + new byte[]{(byte) (firstConnect ? 0x00 : 0x01)})); //possibly not the correct meaning + + return this; + } + + private WatchXPlusDeviceSupport enableDoNotDisturb(TransactionBuilder builder, boolean active) { + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_DO_NOT_DISTURB_SETTINGS, + WatchXPlusConstants.WRITE_VALUE, + new byte[]{(byte) (active ? 0x01 : 0x00)})); + + return this; + } + + private void enableCalibration(boolean enable) { + try { + TransactionBuilder builder = performInitialized("enableCalibration"); + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_CALIBRATION_INIT_TASK, + WatchXPlusConstants.TASK, + new byte[]{(byte) (enable ? 0x01 : 0x00)})); + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to start/stop calibration mode", e); + } + } + + private void holdCalibration() { + try { + TransactionBuilder builder = performInitialized("holdCalibration"); + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_CALIBRATION_KEEP_ALIVE, + WatchXPlusConstants.KEEP_ALIVE)); + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to keep calibration mode alive", e); + } + } + + private void sendCalibrationData(@IntRange(from=0,to=23)int hour, @IntRange(from=0,to=59)int minute, @IntRange(from=0,to=59)int second) { + try { + isCalibrationActive = true; + TransactionBuilder builder = performInitialized("calibrate"); + int handsPosition = ((hour % 12) * 60 + minute) * 60 + second; + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_CALIBRATION_TASK, + WatchXPlusConstants.TASK, + Conversion.toByteArr16(handsPosition))); + performImmediately(builder); + } catch (IOException e) { + isCalibrationActive = false; + LOG.warn("Unable to send calibration data", e); + } + } + + private void getTime() { + try { + TransactionBuilder builder = performInitialized("getTime"); + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_TIME_SETTINGS, + WatchXPlusConstants.READ_VALUE)); + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to get device time", e); + } + } + + private void handleTime(byte[] time) { + GregorianCalendar now = BLETypeConversions.createCalendar(); + GregorianCalendar nowDevice = BLETypeConversions.createCalendar(); + int year = (nowDevice.get(Calendar.YEAR) / 100) * 100 + Conversion.fromBcd8(time[8]); + nowDevice.set(year, + Conversion.fromBcd8(time[9]) - 1, + Conversion.fromBcd8(time[10]), + Conversion.fromBcd8(time[11]), + Conversion.fromBcd8(time[12]), + Conversion.fromBcd8(time[13])); + nowDevice.set(Calendar.DAY_OF_WEEK, Conversion.fromBcd8(time[16]) + 1); + + long timeDiff = (Math.abs(now.getTimeInMillis() - nowDevice.getTimeInMillis())) / 1000; + if (10 < timeDiff && timeDiff < 120) { + enableCalibration(true); + setTime(BLETypeConversions.createCalendar()); + enableCalibration(false); + } + } + + private void setTime(Calendar calendar) { + try { + TransactionBuilder builder = performInitialized("setTime"); + int timezoneOffsetMinutes = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000); + int timezoneOffsetIndustrialMinutes = Math.round((Math.abs(timezoneOffsetMinutes) % 60) * 100f / 60f); + byte[] time = new byte[]{Conversion.toBcd8(calendar.get(Calendar.YEAR) % 100), + Conversion.toBcd8(calendar.get(Calendar.MONTH) + 1), + Conversion.toBcd8(calendar.get(Calendar.DAY_OF_MONTH)), + Conversion.toBcd8(calendar.get(Calendar.HOUR_OF_DAY)), + Conversion.toBcd8(calendar.get(Calendar.MINUTE)), + Conversion.toBcd8(calendar.get(Calendar.SECOND)), + (byte) (timezoneOffsetMinutes / 60), + (byte) timezoneOffsetIndustrialMinutes, + (byte) (calendar.get(Calendar.DAY_OF_WEEK) - 1) + }; + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_TIME_SETTINGS, + WatchXPlusConstants.WRITE_VALUE, + time)); + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to set time", e); + } + } + + public WatchXPlusDeviceSupport getFirmwareVersion(TransactionBuilder builder) { + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_FIRMWARE_INFO, + WatchXPlusConstants.READ_VALUE)); + + return this; + } + + private WatchXPlusDeviceSupport getBatteryState(TransactionBuilder builder) { + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_BATTERY_INFO, + WatchXPlusConstants.READ_VALUE)); + + return this; + } + + private WatchXPlusDeviceSupport setFitnessGoal(TransactionBuilder builder) { + int fitnessGoal = new ActivityUser().getStepsGoal(); + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_FITNESS_GOAL_SETTINGS, + WatchXPlusConstants.WRITE_VALUE, + Conversion.toByteArr16(fitnessGoal))); + + return this; + } + + public WatchXPlusDeviceSupport initialize(TransactionBuilder builder) { + getFirmwareVersion(builder) + .getBatteryState(builder) + .enableNotificationChannels(builder) + .enableDoNotDisturb(builder, false) + .setFitnessGoal(builder); + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); + builder.setGattCallback(this); + + return this; + } + + @Override + public void onDeleteNotification(int id) { + + } + + @Override + public void onSetTime() { + getTime(); + } + + @Override + public void onSetAlarms(ArrayList alarms) { + try { + TransactionBuilder builder = performInitialized("setAlarms"); + for (Alarm alarm : alarms) { + setAlarm(alarm, alarm.getPosition() + 1, builder); + } + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("Unable to set alarms", e); + } + } + + // No useful use case at the moment, used to clear alarm slots for testing. + private void deleteAlarm(TransactionBuilder builder, int index) { + if (0 < index && index < 4) { + byte[] alarmValue = new byte[]{(byte) index, 0x00, 0x00, 0x00, 0x00, 0x00}; + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_ALARM_SETTINGS, + WatchXPlusConstants.WRITE_VALUE, + alarmValue)); + } + } + + private void setAlarm(Alarm alarm, int index, TransactionBuilder builder) { + // Shift the GB internal repetition mask to match the device specific one. + byte repetitionMask = (byte) ((alarm.getRepetition() << 1) | (alarm.isRepetitive() ? 0x80 : 0x00)); + repetitionMask |= (alarm.getRepetition(Alarm.ALARM_SUN) ? 0x01 : 0x00); + if (0 < index && index < 4) { + byte[] alarmValue = new byte[]{(byte) index, + Conversion.toBcd8(AlarmUtils.toCalendar(alarm).get(Calendar.HOUR_OF_DAY)), + Conversion.toBcd8(AlarmUtils.toCalendar(alarm).get(Calendar.MINUTE)), + repetitionMask, + (byte) (alarm.getEnabled() ? 0x01 : 0x00), + 0x00 // TODO: Unknown + }; + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_ALARM_SETTINGS, + WatchXPlusConstants.WRITE_VALUE, + alarmValue)); + } + } + + @Override + public void onSetCallState(CallSpec callSpec) { + switch (callSpec.command) { + case CallSpec.CALL_INCOMING: + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); + break; + case CallSpec.CALL_START: + case CallSpec.CALL_END: +// sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, true); +// break; + default: + break; + } + } + + @Override + public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { + + } + + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + + } + + @Override + public void onSetMusicInfo(MusicSpec musicSpec) { + + } + + @Override + public void onEnableRealtimeSteps(boolean enable) { + + } + + @Override + public void onInstallApp(Uri uri) { + + } + + @Override + public void onAppInfoReq() { + + } + + @Override + public void onAppStart(UUID uuid, boolean start) { + + } + + @Override + public void onAppDelete(UUID uuid) { + + } + + @Override + public void onAppConfiguration(UUID appUuid, String config, Integer id) { + + } + + @Override + public void onAppReorder(UUID[] uuids) { + + } + + @Override + public void onFetchRecordedData(int dataTypes) { + + } + + @Override + public void onReset(int flags) { + + } + + @Override + public void onHeartRateTest() { + + } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + + } + + @Override + public void onFindDevice(boolean start) { + + } + + @Override + public void onSetConstantVibration(int integer) { + + } + + @Override + public void onScreenshotReq() { + + } + + @Override + public void onEnableHeartRateSleepSupport(boolean enable) { + + } + + @Override + public void onSetHeartRateMeasurementInterval(int seconds) { + + } + + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + + } + + @Override + public void onSendConfiguration(String config) { + TransactionBuilder builder; + try { + builder = performInitialized("sendConfig: " + config); + switch (config) { + case ActivityUser.PREF_USER_STEPS_GOAL: + setFitnessGoal(builder); + break; + } + builder.queue(getQueue()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onReadConfiguration(String config) { + + } + + @Override + public void onTestNewFunction() { + + } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + super.onCharacteristicChanged(gatt, characteristic); + + UUID characteristicUUID = characteristic.getUuid(); + if (WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE.equals(characteristicUUID)) { + byte[] value = characteristic.getValue(); + if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_FIRMWARE_INFO, 5)) { + handleFirmwareInfo(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_BUTTON_INDICATOR, 5)) { + LOG.info("Unhandled action: Button pressed"); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_ALARM_INDICATOR, 5)) { + LOG.info("Alarm active: id=" + value[8]); + } else if (isCalibrationActive && value.length == 7 && value[4] == ACK_CALIBRATION) { + setTime(BLETypeConversions.createCalendar()); + isCalibrationActive = false; + } + + return true; + } else { + LOG.info("Unhandled characteristic changed: " + characteristicUUID); + logMessageContent(characteristic.getValue()); + } + + return false; + } + + private byte[] buildCommand(byte[] command, byte action) { + return buildCommand(command, action, null); + } + + private byte[] buildCommand(byte[] command, byte action, byte[] value) { + if (Arrays.equals(command, WatchXPlusConstants.CMD_CALIBRATION_TASK)) { + ACK_CALIBRATION = (byte) sequenceNumber; + } + command = BLETypeConversions.join(command, value); + byte[] result = new byte[7 + command.length]; + System.arraycopy(WatchXPlusConstants.CMD_HEADER, 0, result, 0, 5); + System.arraycopy(command, 0, result, 6, command.length); + result[2] = (byte) (command.length + 1); + result[3] = WatchXPlusConstants.REQUEST; + result[4] = (byte) sequenceNumber++; + result[5] = action; + result[result.length - 1] = calculateChecksum(result); + + return result; + } + + private byte calculateChecksum(byte[] bytes) { + byte checksum = 0x00; + for (int i = 0; i < bytes.length - 1; i++) { + checksum += (bytes[i] ^ i) & 0xFF; + } + return (byte) (checksum & 0xFF); + } + + private void handleFirmwareInfo(byte[] value) { + versionInfo.fwVersion = String.format(Locale.US,"%d.%d.%d", value[8], value[9], value[10]); + handleGBDeviceEvent(versionInfo); + } + + private void handleBatteryState(byte[] value) { + batteryInfo.state = value[8] == 1 ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_LOW; + batteryInfo.level = value[9]; + handleGBDeviceEvent(batteryInfo); + } + + @Override + public void dispose() { + LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); + broadcastManager.unregisterReceiver(broadcastReceiver); + super.dispose(); + } + + private static class Conversion { + static byte toBcd8(@IntRange(from = 0, to = 99) int value) { + int high = (value / 10) << 4; + int low = value % 10; + return (byte) (high | low); + } + + static int fromBcd8(byte value) { + int high = ((value & 0xF0) >> 4) * 10; + int low = value & 0x0F; + return high + low; + } + + static byte[] toByteArr16(int value) { + return new byte[]{(byte) (value >> 8), (byte) value}; + } + + static byte[] toByteArr32(int value) { + return new byte[]{(byte) (value >> 24), + (byte) (value >> 16), + (byte) (value >> 8), + (byte) value}; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/operations/InitOperation.java new file mode 100644 index 000000000..f0b3333a1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/operations/InitOperation.java @@ -0,0 +1,94 @@ +/* Copyright (C) 2018-2019 maxirnilian + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.watchxplus.operations; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9Constants; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.watchxplus.WatchXPlusDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class InitOperation extends AbstractBTLEOperation{ + + private static final Logger LOG = LoggerFactory.getLogger(InitOperation.class); + + private final TransactionBuilder builder; + private final boolean needsAuth; + private final BluetoothGattCharacteristic cmdCharacteristic = getCharacteristic(Watch9Constants.UUID_CHARACTERISTIC_WRITE); + + public InitOperation(boolean needsAuth, WatchXPlusDeviceSupport support, TransactionBuilder builder) { + super(support); + this.needsAuth = needsAuth; + this.builder = builder; + builder.setGattCallback(this); + } + + @Override + protected void doPerform() throws IOException { + builder.notify(cmdCharacteristic, true); + if (needsAuth) { + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext())); + getSupport().authorizationRequest(builder, needsAuth); + } else { + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + getSupport().initialize(builder); + getSupport().performImmediately(builder); + } + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + UUID characteristicUUID = characteristic.getUuid(); + if (Watch9Constants.UUID_CHARACTERISTIC_WRITE.equals(characteristicUUID) && needsAuth) { + try { + byte[] value = characteristic.getValue(); + getSupport().logMessageContent(value); + if (ArrayUtils.equals(value, Watch9Constants.RESP_AUTHORIZATION_TASK, 5) && value[8] == 0x01) { + TransactionBuilder builder = getSupport().createTransactionBuilder("authInit"); + builder.setGattCallback(this); + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + getSupport().initialize(builder).performImmediately(builder); + } else { + return super.onCharacteristicChanged(gatt, characteristic); + } + } catch (Exception e) { + GB.toast(getContext(), "Error authenticating Watch 9", Toast.LENGTH_LONG, GB.ERROR, e); + } + return true; + } else { + LOG.info("Unhandled characteristic changed: " + characteristicUUID); + return super.onCharacteristicChanged(gatt, characteristic); + } + } + + +} From 290a90ec0e64c156c39af7c2e866e42158ab93e9 Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Fri, 18 Oct 2019 09:42:31 +0200 Subject: [PATCH 005/546] Handle notifications with long body --- .../watchxplus/WatchXPlusConstants.java | 4 +- .../watchxplus/WatchXPlusDeviceSupport.java | 54 ++++++++++++------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java index 143d0d5eb..8069b8361 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java @@ -28,8 +28,8 @@ public final class WatchXPlusConstants { public static final UUID UUID_CHARACTERISTIC_UNKNOWN_3 = UUID.fromString("0000a803-0000-1000-8000-00805f9b34fb"); public static final UUID UUID_CHARACTERISTIC_UNKNOWN_4 = UUID.fromString("0000a804-0000-1000-8000-00805f9b34fb"); - public static final int NOTIFICATION_CHANNEL_DEFAULT = 7; - public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 1024; + public static final int NOTIFICATION_CHANNEL_DEFAULT = 0; + public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 10; public static final byte RESPONSE = 0x13; public static final byte REQUEST = 0x31; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java index 0d4c4869d..3a1ccaa1c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java @@ -142,14 +142,13 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { String senderOrTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title); - String message = StringUtils.truncate(senderOrTitle, 32) + "\0"; -// TODO: Commented out to simplify testing -// if (notificationSpec.subject != null) { -// message += StringUtils.truncate(notificationSpec.subject, 128) + "\n\n"; -// } -// if (notificationSpec.body != null) { -// message += StringUtils.truncate(notificationSpec.body, 128); -// } + String message = StringUtils.truncate(senderOrTitle, 14) + "\0"; + if (notificationSpec.subject != null) { + message += StringUtils.truncate(notificationSpec.subject, 20) + ": "; + } + if (notificationSpec.body != null) { + message += StringUtils.truncate(notificationSpec.body, 64); + } sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_DEFAULT, message); } @@ -159,17 +158,36 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { TransactionBuilder builder = performInitialized("showNotification"); byte[] command = WatchXPlusConstants.CMD_NOTIFICATION_TEXT_TASK; byte[] text = notificationText.getBytes("UTF-8"); - byte[] value = new byte[text.length + 2]; - value[0] = (byte)(notificationChannel); -// TODO: Split message into 9-byte arrays and send them one by one. -// Set the message index to FF to indicate end of message - value[1] = (byte) 0xFF; - System.arraycopy(text, 0, value, 2, text.length); + byte[] messagePart; + + int messageLength = text.length; + int parts = messageLength / 9; + int remainder = messageLength % 9; + +// Increment parts quantity if message length is not multiple of 9 + if (remainder != 0) { + parts++; + } + for (int messageIndex = 0; messageIndex < parts; messageIndex++) { + if(messageIndex+1 != parts || remainder == 0) { + messagePart = new byte[11]; + } else { + messagePart = new byte[remainder+2]; + } + + System.arraycopy(text, messageIndex*9, messagePart, 2, messagePart.length-2); + + if(messageIndex+1 == parts) { + messageIndex = 0xFF; + } + messagePart[0] = (byte)notificationChannel; + messagePart[1] = (byte)messageIndex; + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.KEEP_ALIVE, + messagePart)); + } - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(command, - WatchXPlusConstants.KEEP_ALIVE, - value)); performImmediately(builder); } catch (IOException e) { LOG.warn("Unable to send notification", e); From 34d9bccb863ee93003c3876cb3d775fd245ab052 Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Fri, 18 Oct 2019 23:36:56 +0200 Subject: [PATCH 006/546] Retrieve steps count Tried to set weather, but without success Handle ACK response Enabled alarms --- .../watchxplus/WatchXPlusConstants.java | 7 +- .../WatchXPlusDeviceCoordinator.java | 15 ++- .../watchxplus/WatchXPlusDeviceSupport.java | 96 ++++++++++++++++++- 3 files changed, 106 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java index 8069b8361..89558c21e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java @@ -54,7 +54,9 @@ public final class WatchXPlusConstants { public static final byte[] CMD_AUTHORIZATION_TASK = new byte[]{0x01, 0x05}; public static final byte[] CMD_TIME_SETTINGS = new byte[]{0x01, 0x08}; public static final byte[] CMD_ALARM_SETTINGS = new byte[]{0x01, 0x0A}; + public static final byte[] CMD_WEATHER_SET = new byte[]{0x01, 0x10}; public static final byte[] CMD_BATTERY_INFO = new byte[]{0x01, 0x14}; + public static final byte[] CMD_HEARTRATE_INFO = new byte[]{0x15, 0x03}; public static final byte[] CMD_NOTIFICATION_TASK = new byte[]{0x03, 0x01}; public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06}; @@ -65,15 +67,18 @@ public final class WatchXPlusConstants { public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61}; public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02}; + public static final byte[] CMD_DAY_STEPS_INFO = new byte[]{0x10, 0x03}; public static final byte[] RESP_AUTHORIZATION_TASK = new byte[]{0x01, 0x01, 0x05}; public static final byte[] RESP_BUTTON_INDICATOR = new byte[]{0x04, 0x03, 0x11}; public static final byte[] RESP_ALARM_INDICATOR = new byte[]{-0x80, 0x01, 0x0A}; + public static final byte[] RESP_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03}; + public static final byte[] RESP_HEARTRATE = new byte[]{0x08, 0x15, 0x02}; public static final byte[] RESP_FIRMWARE_INFO = new byte[]{0x08, 0x01, 0x02}; public static final byte[] RESP_TIME_SETTINGS = new byte[]{0x08, 0x01, 0x08}; public static final byte[] RESP_BATTERY_INFO = new byte[]{0x08, 0x01, 0x14}; - public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x08, 0x03, 0x02}; + public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x01, 0x03, 0x02}; public static final String ACTION_ENABLE = "action.watch9.enable"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java index a85ba3882..121c505f0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java @@ -8,17 +8,16 @@ import android.net.Uri; import android.os.Build; import android.os.ParcelUuid; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.util.Collection; import java.util.Collections; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; -import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusConstants; -import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusPairingActivity; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -76,7 +75,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsActivityDataFetching() { - return false; + return true; } @Override @@ -101,7 +100,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override public int getAlarmSlotCount() { - return 0; + return 3; } @Override @@ -111,7 +110,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsHeartRateMeasurement(GBDevice device) { - return false; + return true; } @Override @@ -141,7 +140,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsWeather() { - return false; + return true; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java index 3a1ccaa1c..fb39077e4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java @@ -478,6 +478,22 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onFetchRecordedData(int dataTypes) { + TransactionBuilder builder = null; + try { + builder = performInitialized("fetchData"); + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_DAY_STEPS_INFO, + WatchXPlusConstants.READ_VALUE)); +// TODO: Watch does not return heart rate data after this command. Check why +// builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), +// buildCommand(WatchXPlusConstants.CMD_HEARTRATE_INFO, +// WatchXPlusConstants.READ_VALUE)); + + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to retrieve recorded data", e); + } } @Override @@ -558,7 +574,26 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onSendWeather(WeatherSpec weatherSpec) { + try { + TransactionBuilder builder = performInitialized("setWeather"); + byte[] command = WatchXPlusConstants.CMD_WEATHER_SET; + byte[] weatherInfo = new byte[5]; + +// bArr[8] = (byte) (this.mWeatherType >> 8); +// bArr[9] = (byte) this.mWeatherType; +// bArr[10] = (byte) this.mLowTemp; +// bArr[11] = (byte) this.mHighTemp; +// bArr[12] = (byte) this.mCurrentTemp; + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.KEEP_ALIVE, + weatherInfo)); + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to set time", e); + } } @Override @@ -576,23 +611,71 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_TIME_SETTINGS, 5)) { handleTime(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BUTTON_INDICATOR, 5)) { - LOG.info("Unhandled action: Button pressed"); +// It looks like WatchXPlus doesn't send this action + LOG.info(" Unhandled action: Button pressed"); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_ALARM_INDICATOR, 5)) { - LOG.info("Alarm active: id=" + value[8]); + LOG.info(" Alarm active: id=" + value[8]); } else if (isCalibrationActive && value.length == 7 && value[4] == ACK_CALIBRATION) { setTime(BLETypeConversions.createCalendar()); isCalibrationActive = false; + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DAY_STEPS_INDICATOR, 5)) { + handleStepsInfo(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEARTRATE, 5)) { + LOG.info(" Received Heart rate history"); + } else if (value.length == 7 && value[5] == 0) { +// Not sure if that's necessary + handleAck(); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_NOTIFICATION_SETTINGS, 5)) { + LOG.info(" Received notification settings status"); + } else { + LOG.info(" Unhandled value change for characteristic: " + characteristicUUID); + logMessageContent(characteristic.getValue()); } return true; } else { - LOG.info("Unhandled characteristic changed: " + characteristicUUID); + LOG.info(" Unhandled characteristic changed: " + characteristicUUID); logMessageContent(characteristic.getValue()); } return false; } + private void handleAck() { + try { + TransactionBuilder builder = performInitialized("handleAck"); + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), +// TODO: Below value is ACK status. Find out which value is correct + buildCommand((byte)0x00)); + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to response to ACK", e); + } + } + +// This is only for ACK response + private byte[] buildCommand(byte action) { + byte[] result = new byte[7]; + System.arraycopy(WatchXPlusConstants.CMD_HEADER, 0, result, 0, 5); + + result[2] = (byte) (result.length + 1); + result[3] = WatchXPlusConstants.REQUEST; + result[4] = (byte) sequenceNumber++; + result[5] = action; + result[result.length - 1] = calculateChecksum(result); + + return result; + } + + private void handleStepsInfo(byte[] value) { + int steps = Conversion.fromByteArr16(value[8], value[9]); + if (LOG.isDebugEnabled()) { + LOG.debug(" Received steps count: " + steps); + } +// TODO: save steps to DB + } + private byte[] buildCommand(byte[] command, byte action) { return buildCommand(command, action, null); } @@ -656,6 +739,13 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { static byte[] toByteArr16(int value) { return new byte[]{(byte) (value >> 8), (byte) value}; } + static int fromByteArr16(byte... value) { + int intValue = 0; + for (int i2 = 0; i2 < value.length; i2++) { + intValue += (value[i2] & 255) << (((value.length - 1) - i2) * 8); + } + return intValue; + } static byte[] toByteArr32(int value) { return new byte[]{(byte) (value >> 24), From 4601d827ae210f73cf3eb9e6dedc483d103f7042 Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Sun, 20 Oct 2019 10:34:29 +0200 Subject: [PATCH 007/546] Display calibration button for Watch X Plus Use calibration activity from Watch9 for now Partial heart rate history handling (WIP) --- .../adapter/GBDeviceAdapterv2.java | 11 +- .../watchxplus/WatchXPlusConstants.java | 8 +- .../watchxplus/WatchXPlusDeviceSupport.java | 119 ++++++++++++++---- 3 files changed, 112 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java index f756d65cd..811bbd36c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java @@ -59,6 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9CalibrationActivity; +import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusCalibrationActivity; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; @@ -320,11 +321,17 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter> 8); -// bArr[9] = (byte) this.mWeatherType; -// bArr[10] = (byte) this.mLowTemp; -// bArr[11] = (byte) this.mHighTemp; -// bArr[12] = (byte) this.mCurrentTemp; - - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(command, - WatchXPlusConstants.KEEP_ALIVE, - weatherInfo)); - performImmediately(builder); +// First two bytes are controlling the icon + weatherInfo[0] = 0x00; + weatherInfo[1] = 0x00; + weatherInfo[2] = (byte) weatherSpec.todayMinTemp; + weatherInfo[3] = (byte) weatherSpec.todayMaxTemp; + weatherInfo[4] = (byte) weatherSpec.currentTemp; + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.KEEP_ALIVE, + weatherInfo)); + performImmediately(builder); } catch (IOException e) { - LOG.warn("Unable to set time", e); + LOG.warn("Unable to set weather", e); } } @@ -620,11 +623,15 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { isCalibrationActive = false; } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DAY_STEPS_INDICATOR, 5)) { handleStepsInfo(value); - } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEARTRATE, 5)) { - LOG.info(" Received Heart rate history"); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA, 5)) { + LOG.info(" Received Heart rate data count"); + handleHeartRateDataCount(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_DETAILS, 5)) { + LOG.info(" Received Heart rate data details"); + handleHeartRateDetails(value); } else if (value.length == 7 && value[5] == 0) { -// Not sure if that's necessary - handleAck(); +// Not sure if that's necessary. There is no response for ACK in original app logs +// handleAck(); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_NOTIFICATION_SETTINGS, 5)) { LOG.info(" Received notification settings status"); } else { @@ -641,6 +648,36 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return false; } + private void handleHeartRateDetails(byte[] value) { + calculateHeartRateDetails(value); + LOG.info("Got Heart rate details"); + } + + private void handleHeartRateDataCount(byte[] value) { + + int dataCount = Conversion.fromByteArr16(value[10], value[11]); + try { + TransactionBuilder builder = performInitialized("requestHeartRate"); + byte[] heartRateDataType = WatchXPlusConstants.HEART_RATE_DATA_TYPE; + + LOG.info("Watch contains " + dataCount + " heart rate entries"); +// Request all data samples + for (int i = 0; i < dataCount; i++) { + byte[] index = Conversion.toByteArr16(i); + byte[] req = BLETypeConversions.join(heartRateDataType, index); + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_DETAILS, + WatchXPlusConstants.READ_VALUE, + req)); + + } + performImmediately(builder); + } catch (IOException e) { + LOG.warn("Unable to response to ACK", e); + } + } + private void handleAck() { try { TransactionBuilder builder = performInitialized("handleAck"); @@ -723,6 +760,44 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { super.dispose(); } + private static void calculateHeartRateDetails(byte[] bArr) { + + int timestamp = Conversion.fromByteArr16(bArr[8], bArr[9], bArr[10], bArr[11]); + int dataLength = Conversion.fromByteArr16(bArr[12], bArr[13]); + int samplingInterval = (int) onSamplingInterval(bArr[14] >> 4, Conversion.fromByteArr16((byte) (bArr[14] & 15), bArr[15])); + int mtu = Conversion.fromByteArr16(bArr[16]); + int parts = dataLength / 16; + if (dataLength % 16 > 0) { + parts++; + } + + LOG.info("timestamp (UTC): " + timestamp); + LOG.info("timestamp (UTC): " + new Date(timestamp)); + LOG.info("dataLength (data length): " + dataLength); + LOG.info("samplingInterval (per time): " + samplingInterval); + LOG.info("mtu (mtu): " + mtu); + LOG.info("parts: " + parts); + } + + private static double onSamplingInterval(int i, int i2) { + switch (i) { + case 1: + return 1.0d * Math.pow(10.0d, -6.0d) * ((double) i2); + case 2: + return 1.0d * Math.pow(10.0d, -3.0d) * ((double) i2); + case 3: + return (double) (1 * i2); + case 4: + return 10.0d * Math.pow(10.0d, -6.0d) * ((double) i2); + case 5: + return 10.0d * Math.pow(10.0d, -3.0d) * ((double) i2); + case 6: + return (double) (10 * i2); + default: + return (double) (10 * i2); + } + } + private static class Conversion { static byte toBcd8(@IntRange(from = 0, to = 99) int value) { int high = (value / 10) << 4; From 45054bbcd93933746d66532d452453d38346d647 Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Wed, 23 Oct 2019 22:07:38 +0200 Subject: [PATCH 008/546] Extracted some commons for Lenovo watches --- app/src/main/AndroidManifest.xml | 4 +- .../adapter/GBDeviceAdapterv2.java | 10 +--- .../LenovoWatchCalibrationActivity.java} | 22 ++++---- .../LenovoWatchConstants.java} | 39 +++---------- .../LenovoWatchPairingActivity.java} | 6 +- .../watchxplus/WatchXPlusConstants.java | 55 +++++++++++++++++++ .../WatchXPlusDeviceCoordinator.java | 5 +- .../service/DeviceSupportFactory.java | 2 +- .../operations/InitOperation.java | 5 +- .../watchxplus/WatchXPlusDeviceSupport.java | 6 +- .../gadgetbridge/util/DeviceHelper.java | 2 +- 11 files changed, 91 insertions(+), 65 deletions(-) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/{watchxplus/WatchXPlusCalibrationActivity.java => lenovo/LenovoWatchCalibrationActivity.java} (80%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/{watchxplus/WatchXPlusConstants.java => lenovo/LenovoWatchConstants.java} (65%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/{watchxplus/WatchXPlusPairingActivity.java => lenovo/LenovoWatchPairingActivity.java} (95%) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/{ => lenovo}/watchxplus/WatchXPlusDeviceCoordinator.java (95%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/{watchxplus => lenovo}/operations/InitOperation.java (93%) rename app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/{ => lenovo}/watchxplus/WatchXPlusDeviceSupport.java (99%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1d5f5afc4..e1e1d26f9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -407,10 +407,10 @@ android:name=".devices.watch9.Watch9CalibrationActivity" android:label="@string/title_activity_watch9_calibration" /> . */ -package nodomain.freeyourgadget.gadgetbridge.devices.watchxplus; +package nodomain.freeyourgadget.gadgetbridge.devices.lenovo; import android.content.Intent; import android.os.Bundle; @@ -28,7 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -public class WatchXPlusCalibrationActivity extends AbstractGBActivity { +public class LenovoWatchCalibrationActivity extends AbstractGBActivity { private static final String STATE_DEVICE = "stateDevice"; GBDevice device; @@ -61,7 +61,7 @@ public class WatchXPlusCalibrationActivity extends AbstractGBActivity { holdCalibration = new Runnable() { @Override public void run() { - LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(WatchXPlusConstants.ACTION_CALIBRATION_HOLD)); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(LenovoWatchConstants.ACTION_CALIBRATION_HOLD)); handler.postDelayed(this, 10000); } }; @@ -81,10 +81,10 @@ public class WatchXPlusCalibrationActivity extends AbstractGBActivity { public void onClick(View v) { btCalibrate.setEnabled(false); handler.removeCallbacks(holdCalibration); - Intent calibrationData = new Intent(WatchXPlusConstants.ACTION_CALIBRATION_SEND); - calibrationData.putExtra(WatchXPlusConstants.VALUE_CALIBRATION_HOUR, pickerHour.getValue()); - calibrationData.putExtra(WatchXPlusConstants.VALUE_CALIBRATION_MINUTE, pickerMinute.getValue()); - calibrationData.putExtra(WatchXPlusConstants.VALUE_CALIBRATION_SECOND, pickerSecond.getValue()); + Intent calibrationData = new Intent(LenovoWatchConstants.ACTION_CALIBRATION_SEND); + calibrationData.putExtra(LenovoWatchConstants.VALUE_CALIBRATION_HOUR, pickerHour.getValue()); + calibrationData.putExtra(LenovoWatchConstants.VALUE_CALIBRATION_MINUTE, pickerMinute.getValue()); + calibrationData.putExtra(LenovoWatchConstants.VALUE_CALIBRATION_SECOND, pickerSecond.getValue()); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibrationData); finish(); } @@ -106,8 +106,8 @@ public class WatchXPlusCalibrationActivity extends AbstractGBActivity { @Override protected void onStart() { super.onStart(); - Intent calibration = new Intent(WatchXPlusConstants.ACTION_CALIBRATION); - calibration.putExtra(WatchXPlusConstants.ACTION_ENABLE, true); + Intent calibration = new Intent(LenovoWatchConstants.ACTION_CALIBRATION); + calibration.putExtra(LenovoWatchConstants.ACTION_ENABLE, true); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration); handler.postDelayed(holdCalibration, 1000); } @@ -115,8 +115,8 @@ public class WatchXPlusCalibrationActivity extends AbstractGBActivity { @Override protected void onStop() { super.onStop(); - Intent calibration = new Intent(WatchXPlusConstants.ACTION_CALIBRATION); - calibration.putExtra(WatchXPlusConstants.ACTION_ENABLE, false); + Intent calibration = new Intent(LenovoWatchConstants.ACTION_CALIBRATION); + calibration.putExtra(LenovoWatchConstants.ACTION_ENABLE, false); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration); handler.removeCallbacks(holdCalibration); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchConstants.java similarity index 65% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchConstants.java index b79d032ad..81365035c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchConstants.java @@ -14,22 +14,9 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.devices.watchxplus; +package nodomain.freeyourgadget.gadgetbridge.devices.lenovo; -import java.util.UUID; - -public final class WatchXPlusConstants { - public static final UUID UUID_SERVICE_WATCHXPLUS = UUID.fromString("0000a800-0000-1000-8000-00805f9b34fb"); - - public static final UUID UUID_UNKNOWN_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - - public static final UUID UUID_CHARACTERISTIC_WRITE = UUID.fromString("0000a801-0000-1000-8000-00805f9b34fb"); - public static final UUID UUID_CHARACTERISTIC_UNKNOWN_2 = UUID.fromString("0000a802-0000-1000-8000-00805f9b34fb"); - public static final UUID UUID_CHARACTERISTIC_UNKNOWN_3 = UUID.fromString("0000a803-0000-1000-8000-00805f9b34fb"); - public static final UUID UUID_CHARACTERISTIC_UNKNOWN_4 = UUID.fromString("0000a804-0000-1000-8000-00805f9b34fb"); - - public static final int NOTIFICATION_CHANNEL_DEFAULT = 0; - public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 10; +public class LenovoWatchConstants { public static final byte RESPONSE = 0x13; public static final byte REQUEST = 0x31; @@ -54,14 +41,9 @@ public final class WatchXPlusConstants { public static final byte[] CMD_AUTHORIZATION_TASK = new byte[]{0x01, 0x05}; public static final byte[] CMD_TIME_SETTINGS = new byte[]{0x01, 0x08}; public static final byte[] CMD_ALARM_SETTINGS = new byte[]{0x01, 0x0A}; - public static final byte[] CMD_WEATHER_SET = new byte[]{0x01, 0x10}; public static final byte[] CMD_BATTERY_INFO = new byte[]{0x01, 0x14}; - public static final byte[] CMD_RETRIEVE_DATA = new byte[]{(byte)0xF0, 0x10}; - public static final byte[] CMD_RETRIEVE_DATA_DETAILS = new byte[]{(byte)0xF0, 0x11}; - public static final byte[] HEART_RATE_DATA_TYPE = new byte[]{0x00, 0x02}; public static final byte[] CMD_NOTIFICATION_TASK = new byte[]{0x03, 0x01}; - public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06}; public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02}; public static final byte[] CMD_CALIBRATION_INIT_TASK = new byte[]{0x03, 0x31}; public static final byte[] CMD_CALIBRATION_TASK = new byte[]{0x03, 0x33, 0x01}; @@ -69,34 +51,29 @@ public final class WatchXPlusConstants { public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61}; public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02}; - public static final byte[] CMD_DAY_STEPS_INFO = new byte[]{0x10, 0x03}; public static final byte[] RESP_AUTHORIZATION_TASK = new byte[]{0x01, 0x01, 0x05}; public static final byte[] RESP_BUTTON_INDICATOR = new byte[]{0x04, 0x03, 0x11}; public static final byte[] RESP_ALARM_INDICATOR = new byte[]{-0x80, 0x01, 0x0A}; - public static final byte[] RESP_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03}; - public static final byte[] RESP_HEARTRATE = new byte[]{-0x80, 0x15, 0x03}; public static final byte[] RESP_FIRMWARE_INFO = new byte[]{0x08, 0x01, 0x02}; public static final byte[] RESP_TIME_SETTINGS = new byte[]{0x08, 0x01, 0x08}; public static final byte[] RESP_BATTERY_INFO = new byte[]{0x08, 0x01, 0x14}; public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x01, 0x03, 0x02}; - public static final byte[] RESP_HEART_RATE_DATA = new byte[]{0x08, (byte)0xF0, 0x10}; - public static final byte[] RESP_HEART_RATE_DATA_DETAILS = new byte[]{0x08, (byte)0xF0, 0x11}; public static final String ACTION_ENABLE = "action.watch9.enable"; public static final String ACTION_CALIBRATION - = "nodomain.freeyourgadget.gadgetbridge.devices.action.watchxplus.start_calibration"; + = "nodomain.freeyourgadget.gadgetbridge.devices.action.lenovowatch.start_calibration"; public static final String ACTION_CALIBRATION_SEND - = "nodomain.freeyourgadget.gadgetbridge.devices.action.watchxplus.send_calibration"; + = "nodomain.freeyourgadget.gadgetbridge.devices.action.lenovowatch.send_calibration"; public static final String ACTION_CALIBRATION_HOLD - = "nodomain.freeyourgadget.gadgetbridge.devices.action.watchxplus.keep_calibrating"; + = "nodomain.freeyourgadget.gadgetbridge.devices.action.lenovowatch.keep_calibrating"; public static final String VALUE_CALIBRATION_HOUR - = "value.watch9.calibration_hour"; + = "value.lenovowatch.calibration_hour"; public static final String VALUE_CALIBRATION_MINUTE - = "value.watch9.calibration_minute"; + = "value.lenovowatch.calibration_minute"; public static final String VALUE_CALIBRATION_SECOND - = "value.watch9.calibration_second"; + = "value.lenovowatch.calibration_second"; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java similarity index 95% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusPairingActivity.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java index 79b209c51..2b6f795ae 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusPairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/LenovoWatchPairingActivity.java @@ -14,7 +14,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.devices.watchxplus; +package nodomain.freeyourgadget.gadgetbridge.devices.lenovo; import android.content.BroadcastReceiver; import android.content.Context; @@ -40,8 +40,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; -public class WatchXPlusPairingActivity extends AbstractGBActivity { - private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusPairingActivity.class); +public class LenovoWatchPairingActivity extends AbstractGBActivity { + private static final Logger LOG = LoggerFactory.getLogger(LenovoWatchPairingActivity.class); private static final String STATE_DEVICE_CANDIDATE = "stateDeviceCandidate"; 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 new file mode 100644 index 000000000..afaac3955 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusConstants.java @@ -0,0 +1,55 @@ +/* Copyright (C) 2018-2019 maxirnilian + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus; + +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.LenovoWatchConstants; + +public final class WatchXPlusConstants extends LenovoWatchConstants { + public static final UUID UUID_SERVICE_WATCHXPLUS = UUID.fromString("0000a800-0000-1000-8000-00805f9b34fb"); + + public static final UUID UUID_UNKNOWN_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + + public static final UUID UUID_CHARACTERISTIC_WRITE = UUID.fromString("0000a801-0000-1000-8000-00805f9b34fb"); + public static final UUID UUID_CHARACTERISTIC_UNKNOWN_2 = UUID.fromString("0000a802-0000-1000-8000-00805f9b34fb"); + public static final UUID UUID_CHARACTERISTIC_UNKNOWN_3 = UUID.fromString("0000a803-0000-1000-8000-00805f9b34fb"); + public static final UUID UUID_CHARACTERISTIC_UNKNOWN_4 = UUID.fromString("0000a804-0000-1000-8000-00805f9b34fb"); + + public static final int NOTIFICATION_CHANNEL_DEFAULT = 0; + public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 10; + + public static final byte[] CMD_WEATHER_SET = new byte[]{0x01, 0x10}; + public static final byte[] CMD_RETRIEVE_DATA = new byte[]{(byte)0xF0, 0x10}; + public static final byte[] CMD_RETRIEVE_DATA_DETAILS = new byte[]{(byte)0xF0, 0x11}; + public static final byte[] HEART_RATE_DATA_TYPE = new byte[]{0x00, 0x02}; + + public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06}; + public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02}; + public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61}; + + public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02}; + public static final byte[] CMD_DAY_STEPS_INFO = new byte[]{0x10, 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}; + public static final byte[] RESP_HEARTRATE = new byte[]{-0x80, 0x15, 0x03}; + + public static final byte[] RESP_HEART_RATE_DATA = new byte[]{0x08, (byte)0xF0, 0x10}; + public static final byte[] RESP_HEART_RATE_DATA_DETAILS = new byte[]{0x08, (byte)0xF0, 0x11}; + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java similarity index 95% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java index 121c505f0..ce2376871 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watchxplus/WatchXPlusDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusDeviceCoordinator.java @@ -1,4 +1,4 @@ -package nodomain.freeyourgadget.gadgetbridge.devices.watchxplus; +package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus; import android.annotation.TargetApi; import android.app.Activity; @@ -18,6 +18,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.LenovoWatchPairingActivity; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -70,7 +71,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Nullable @Override public Class getPairingActivity() { - return WatchXPlusPairingActivity.class; + return LenovoWatchPairingActivity.class; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java index dba93c3aa..2d7584545 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -52,7 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport; -import nodomain.freeyourgadget.gadgetbridge.service.devices.watchxplus.WatchXPlusDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus.WatchXPlusDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.zetime.ZeTimeDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.util.GB; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java similarity index 93% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/operations/InitOperation.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java index f0b3333a1..f59b66f4e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/operations/InitOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java @@ -14,7 +14,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.service.devices.watchxplus.operations; +package nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.operations; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; @@ -31,8 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; -import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport; -import nodomain.freeyourgadget.gadgetbridge.service.devices.watchxplus.WatchXPlusDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus.WatchXPlusDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java similarity index 99% rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java index dc21d8618..77f04eff8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/watchxplus/WatchXPlusDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/watchxplus/WatchXPlusDeviceSupport.java @@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package nodomain.freeyourgadget.gadgetbridge.service.devices.watchxplus; +package nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; @@ -42,7 +42,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; -import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; @@ -59,7 +59,7 @@ 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.devices.watchxplus.operations.InitOperation; +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.StringUtils; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index f83c00535..c3a9ede3e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -68,7 +68,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator; -import nodomain.freeyourgadget.gadgetbridge.devices.watchxplus.WatchXPlusDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator; From 18d1a7ddc7a58d8037ef9a1b9241b2b1e13768e9 Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Wed, 23 Oct 2019 23:32:40 +0200 Subject: [PATCH 009/546] Use builder.queue() instead of performImmediately() --- .../watchxplus/WatchXPlusDeviceSupport.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 77f04eff8..524a33415 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 @@ -189,7 +189,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { messagePart)); } - performImmediately(builder); + builder.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to send notification", e); } @@ -269,7 +269,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_TIME_SETTINGS, WatchXPlusConstants.READ_VALUE)); - performImmediately(builder); + builder.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to get device time", e); } @@ -314,7 +314,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { buildCommand(WatchXPlusConstants.CMD_TIME_SETTINGS, WatchXPlusConstants.WRITE_VALUE, time)); - performImmediately(builder); + builder.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to set time", e); } @@ -493,7 +493,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { WatchXPlusConstants.READ_VALUE, WatchXPlusConstants.HEART_RATE_DATA_TYPE)); - performImmediately(builder); + builder.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to retrieve recorded data", e); } @@ -593,7 +593,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { buildCommand(command, WatchXPlusConstants.KEEP_ALIVE, weatherInfo)); - performImmediately(builder); + builder.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to set weather", e); } @@ -630,6 +630,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { LOG.info(" Received Heart rate data details"); handleHeartRateDetails(value); } else if (value.length == 7 && value[5] == 0) { + LOG.info(" Received ACK"); // Not sure if that's necessary. There is no response for ACK in original app logs // handleAck(); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_NOTIFICATION_SETTINGS, 5)) { @@ -672,7 +673,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { req)); } - performImmediately(builder); + builder.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to response to ACK", e); } @@ -683,9 +684,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { TransactionBuilder builder = performInitialized("handleAck"); builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), -// TODO: Below value is ACK status. Find out which value is correct buildCommand((byte)0x00)); - performImmediately(builder); + builder.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to response to ACK", e); } @@ -696,7 +696,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { byte[] result = new byte[7]; System.arraycopy(WatchXPlusConstants.CMD_HEADER, 0, result, 0, 5); - result[2] = (byte) (result.length + 1); + result[2] = (byte) (result.length - 6); result[3] = WatchXPlusConstants.REQUEST; result[4] = (byte) sequenceNumber++; result[5] = action; From 4728d5b4d08fc3c0a7c19f093aeaf2f07ccfe358 Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Thu, 24 Oct 2019 22:23:45 +0200 Subject: [PATCH 010/546] Perform Watch X Plus authorization request on each connect --- .../service/devices/lenovo/operations/InitOperation.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java index f59b66f4e..c83cadbee 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java @@ -53,14 +53,11 @@ public class InitOperation extends AbstractBTLEOperation Date: Sun, 27 Oct 2019 18:04:38 +0100 Subject: [PATCH 011/546] Added blood pressure measurement request and response handling. Added heart rate history fetching (watch doesn't return data yet). Steps data is saved to DB. --- .../gadgetbridge/daogen/GBDaoGenerator.java | 15 + .../watchxplus/WatchXPlusConstants.java | 8 +- .../WatchXPlusDeviceCoordinator.java | 4 +- .../watchxplus/WatchXPlusSampleProvider.java | 68 ++++ .../watchxplus/WatchXPlusDeviceSupport.java | 324 +++++++++++++++--- 5 files changed, 363 insertions(+), 56 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 13868d787..ac073ec7e 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -72,6 +72,7 @@ public class GBDaoGenerator { addXWatchActivitySample(schema, user, device); addZeTimeActivitySample(schema, user, device); addID115ActivitySample(schema, user, device); + addWatchXPlusHealthActivitySample(schema, user, device); addCalendarSyncState(schema, device); addAlarms(schema, user, device); @@ -330,6 +331,20 @@ public class GBDaoGenerator { return activitySample; } + private static Entity addWatchXPlusHealthActivitySample(Schema schema, Entity user, Entity device) { + Entity activitySample = addEntity(schema, "WatchXPlusActivitySample"); + activitySample.implementsSerializable(); + addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device); + activitySample.addByteArrayProperty("rawWatchXPlusHealthData"); + activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey(); +// activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE); + addHeartRateProperties(activitySample); + activitySample.addIntProperty("distance"); + activitySample.addIntProperty("calories"); + return activitySample; + } + private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) { activitySample.setSuperclass(superClass); activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider"); 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 afaac3955..4bf09b94d 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 @@ -34,9 +34,11 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 10; public static final byte[] CMD_WEATHER_SET = new byte[]{0x01, 0x10}; - public static final byte[] CMD_RETRIEVE_DATA = new byte[]{(byte)0xF0, 0x10}; + public static final byte[] CMD_RETRIEVE_DATA_COUNT = new byte[]{(byte)0xF0, 0x10}; public static final byte[] CMD_RETRIEVE_DATA_DETAILS = new byte[]{(byte)0xF0, 0x11}; + public static final byte[] CMD_RETRIEVE_DATA_CONTENT = new byte[]{(byte)0xF0, 0x12}; public static final byte[] HEART_RATE_DATA_TYPE = new byte[]{0x00, 0x02}; + public static final byte[] CMD_BLOOD_PRESSURE_MEASURE = new byte[]{0x05, 0x0D}; public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06}; public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02}; @@ -49,7 +51,9 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] RESP_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03}; public static final byte[] RESP_HEARTRATE = new byte[]{-0x80, 0x15, 0x03}; - public static final byte[] RESP_HEART_RATE_DATA = new byte[]{0x08, (byte)0xF0, 0x10}; + public static final byte[] RESP_HEART_RATE_DATA_COUNT = new byte[]{0x08, (byte)0xF0, 0x10}; public static final byte[] RESP_HEART_RATE_DATA_DETAILS = new byte[]{0x08, (byte)0xF0, 0x11}; + public static final byte[] RESP_HEART_RATE_DATA_CONTENT = new byte[]{0x08, (byte)0xF0, 0x12}; + public static final byte[] RESP_BP_MEASURE_STARTED = new byte[]{0x08, 0x05, 0x0D}; } 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 ce2376871..ddf5007d9 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 @@ -81,12 +81,12 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { @Override public boolean supportsActivityTracking() { - return false; + return true; } @Override public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { - return null; + return new WatchXPlusSampleProvider(device, session); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java new file mode 100644 index 000000000..0c596ab0c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java @@ -0,0 +1,68 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +public class WatchXPlusSampleProvider extends AbstractSampleProvider { + + private GBDevice mDevice; + private DaoSession mSession; + + public WatchXPlusSampleProvider(GBDevice device, DaoSession session) { + super(device, session); + + mSession = session; + mDevice = device; + } + + @Override + public int normalizeType(int rawType) { + return rawType; + } + + @Override + public int toRawActivityKind(int activityKind) { + return activityKind; + } + + @Override + public float normalizeIntensity(int rawIntensity) { + return rawIntensity; + } + + @Override + public WatchXPlusActivitySample createActivitySample() { + return new WatchXPlusActivitySample(); + } + + @Override + public AbstractDao getSampleDao() { + return getSession().getWatchXPlusActivitySampleDao(); + } + + @Nullable + @Override + protected Property getRawKindSampleProperty() { + return WatchXPlusActivitySampleDao.Properties.RawKind; + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return WatchXPlusActivitySampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return WatchXPlusActivitySampleDao.Properties.DeviceId; + } +} 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 524a33415..db48bca2b 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 @@ -37,13 +37,23 @@ import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; +import java.util.List; import java.util.Locale; +import java.util.TimeZone; import java.util.UUID; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; @@ -70,6 +80,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { private int sequenceNumber = 0; private boolean isCalibrationActive = false; + private List heartRateDataToFetch = new ArrayList<>(); + private int requestedHeartRateTimestamp; + private int heartRateDataSlots; + private byte ACK_CALIBRATION = 0; private final GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo(); @@ -170,19 +184,19 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { parts++; } for (int messageIndex = 0; messageIndex < parts; messageIndex++) { - if(messageIndex+1 != parts || remainder == 0) { + if (messageIndex + 1 != parts || remainder == 0) { messagePart = new byte[11]; } else { - messagePart = new byte[remainder+2]; + messagePart = new byte[remainder + 2]; } - System.arraycopy(text, messageIndex*9, messagePart, 2, messagePart.length-2); + System.arraycopy(text, messageIndex * 9, messagePart, 2, messagePart.length - 2); - if(messageIndex+1 == parts) { + if (messageIndex + 1 == parts) { messageIndex = 0xFF; } - messagePart[0] = (byte)notificationChannel; - messagePart[1] = (byte)messageIndex; + messagePart[0] = (byte) notificationChannel; + messagePart[1] = (byte) messageIndex; builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(command, WatchXPlusConstants.KEEP_ALIVE, @@ -247,7 +261,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } - private void sendCalibrationData(@IntRange(from=0,to=23)int hour, @IntRange(from=0,to=59)int minute, @IntRange(from=0,to=59)int second) { + private void sendCalibrationData(@IntRange(from = 0, to = 23) int hour, @IntRange(from = 0, to = 59) int minute, @IntRange(from = 0, to = 59) int second) { try { isCalibrationActive = true; TransactionBuilder builder = performInitialized("calibrate"); @@ -488,10 +502,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { WatchXPlusConstants.READ_VALUE)); // Fetch heart rate data samples count - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA, - WatchXPlusConstants.READ_VALUE, - WatchXPlusConstants.HEART_RATE_DATA_TYPE)); + requestHeartRateDataCount(builder); builder.queue(getQueue()); } catch (IOException e) { @@ -572,7 +583,22 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onTestNewFunction() { + requestBloodPressureMeasurement(); + } + private void requestBloodPressureMeasurement() { + try { + TransactionBuilder builder = performInitialized("bpMeasure"); + + byte[] command = WatchXPlusConstants.CMD_BLOOD_PRESSURE_MEASURE; + + 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 BP Measure", e); + } } @Override @@ -607,7 +633,9 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { UUID characteristicUUID = characteristic.getUuid(); if (WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE.equals(characteristicUUID)) { byte[] value = characteristic.getValue(); - if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_FIRMWARE_INFO, 5)) { + if (value[0] != 0x23) { + handleHeartRateContentDataChunk(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_FIRMWARE_INFO, 5)) { handleFirmwareInfo(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BATTERY_INFO, 5)) { handleBatteryState(value); @@ -623,12 +651,17 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { isCalibrationActive = false; } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DAY_STEPS_INDICATOR, 5)) { handleStepsInfo(value); - } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA, 5)) { + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_COUNT, 5)) { LOG.info(" Received Heart rate data count"); handleHeartRateDataCount(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_DETAILS, 5)) { LOG.info(" Received Heart rate data details"); handleHeartRateDetails(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_CONTENT, 5)) { + LOG.info(" Received Heart rate data content"); + handleHeartRateContentAck(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BP_MEASURE_STARTED, 5)) { + handleBpMeasureResult(value); } else if (value.length == 7 && value[5] == 0) { LOG.info(" Received ACK"); // Not sure if that's necessary. There is no response for ACK in original app logs @@ -649,33 +682,145 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return false; } - private void handleHeartRateDetails(byte[] value) { - calculateHeartRateDetails(value); - LOG.info("Got Heart rate details"); + /** + * Heart rate history retrieve flow: + * 1. Request for heart rate data slots count. CMD_RETRIEVE_DATA_COUNT, {@link WatchXPlusDeviceSupport#requestHeartRateDataCount} + * 2. Extract data count from response. RESP_HEART_RATE_DATA_COUNT, {@link WatchXPlusDeviceSupport#handleHeartRateDataCount} + * 3. Request for N data slot details. CMD_RETRIEVE_DATA_DETAILS, {@link WatchXPlusDeviceSupport#requestHeartRateDetails} + * 4. Timestamp of slot is returned, save it for later use. RESP_HEART_RATE_DATA_DETAILS, {@link WatchXPlusDeviceSupport#handleHeartRateDetails} + * 5. Repeat step 3-4 until all slots details retrieved. + * 6. Request for M data content by timestamp. CMD_RETRIEVE_DATA_CONTENT, {@link WatchXPlusDeviceSupport#requestHeartRateContentForTimestamp} + * 7. Receive kind of pre-flight response. RESP_HEART_RATE_DATA_CONTENT, {@link WatchXPlusDeviceSupport#handleHeartRateContentAck} + * 8. Receive frames with content. They are different than other frames, {@link WatchXPlusDeviceSupport#handleHeartRateContentDataChunk} + * ie. 0000000255-4F4C48-434241434444454648474747, 0001000247-474645-434240FFFFFFFFFFFFFFFFFF + */ + private void requestHeartRateDataCount(TransactionBuilder builder) { + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_COUNT, + WatchXPlusConstants.READ_VALUE, + WatchXPlusConstants.HEART_RATE_DATA_TYPE)); } private void handleHeartRateDataCount(byte[] value) { int dataCount = Conversion.fromByteArr16(value[10], value[11]); + LOG.info("Watch contains " + dataCount + " heart rate entries"); + this.heartRateDataSlots = dataCount; + heartRateDataToFetch.clear(); + if (dataCount != 0) { + requestHeartRateDetails(heartRateDataToFetch.size()); + } + } + + private void requestHeartRateDetails(int i) { try { TransactionBuilder builder = performInitialized("requestHeartRate"); byte[] heartRateDataType = WatchXPlusConstants.HEART_RATE_DATA_TYPE; - LOG.info("Watch contains " + dataCount + " heart rate entries"); -// Request all data samples - for (int i = 0; i < dataCount; i++) { - byte[] index = Conversion.toByteArr16(i); - byte[] req = BLETypeConversions.join(heartRateDataType, index); + byte[] index = Conversion.toByteArr16(i); + byte[] req = BLETypeConversions.join(heartRateDataType, index); - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_DETAILS, - WatchXPlusConstants.READ_VALUE, - req)); + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_DETAILS, + WatchXPlusConstants.READ_VALUE, + req)); - } builder.queue(getQueue()); } catch (IOException e) { - LOG.warn("Unable to response to ACK", e); + LOG.warn("Unable to request data", e); + } + } + + private void handleHeartRateDetails(byte[] value) { + LOG.info("Got Heart rate details"); + int timestamp = Conversion.fromByteArr16(value[8], value[9], value[10], value[11]); + int dataLength = Conversion.fromByteArr16(value[12], value[13]); + int samplingInterval = (int) onSamplingInterval(value[14] >> 4, Conversion.fromByteArr16((byte) (value[14] & 15), value[15])); + int mtu = Conversion.fromByteArr16(value[16]); + int parts = dataLength / 16; + if (dataLength % 16 > 0) { + parts++; + } + + LOG.info("timestamp (UTC): " + timestamp); + LOG.info("timestamp (UTC): " + new Date((long) timestamp * 1000)); + LOG.info("dataLength (data length): " + dataLength); + LOG.info("samplingInterval (per time): " + samplingInterval); + LOG.info("mtu (mtu): " + mtu); + LOG.info("parts: " + parts); + + heartRateDataToFetch.add(timestamp); + + if (heartRateDataToFetch.size() == heartRateDataSlots) { + requestHeartRateContentForTimestamp(heartRateDataToFetch.get(0)); + } else { + requestHeartRateDetails(heartRateDataToFetch.size()); + } + } + + private void requestHeartRateContentForTimestamp(int timestamp) { + byte[] heartRateDataType = WatchXPlusConstants.HEART_RATE_DATA_TYPE; + byte[] command = WatchXPlusConstants.CMD_RETRIEVE_DATA_CONTENT; + + try { + TransactionBuilder builder = performInitialized("content"); + byte[] ts = Conversion.toByteArr32(timestamp); + byte[] req = BLETypeConversions.join(heartRateDataType, ts); + req = BLETypeConversions.join(req, Conversion.toByteArr16(0)); + requestedHeartRateTimestamp = timestamp; + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.READ_VALUE, + req)); + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("Unable to request heart rate content", e); + } + } + + + private void handleHeartRateContentAck(byte[] value) { + LOG.info(" Received heart rate data content start"); + } + + private void handleHeartRateContentDataChunk(byte[] value) { + int chunkNo = Conversion.fromByteArr16(value[0], value[1]); + int dataType = Conversion.fromByteArr16(value[2], value[2]); + int timezoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis()); + if (dataType != 2) { + LOG.warn(" Got unsupported data package type: " + dataType); + } else { + for (int i = 4; i < value.length; i++) { + + int val = Conversion.fromByteArr16(value[i]); + if (255 == val) { + break; + } + int tsWithOffset = requestedHeartRateTimestamp + (((((chunkNo * 16) + i) - 4) * 2) * 60) - timezoneOffset; + LOG.info(" Got HR data: " + new Date(tsWithOffset) + ", value: " + val); + } + + heartRateDataToFetch.remove(0); + if (!heartRateDataToFetch.isEmpty()) { + requestHeartRateContentForTimestamp(heartRateDataToFetch.get(0)); + } else { + heartRateDataSlots = 0; + } + } + } + + + private void handleBpMeasureResult(byte[] value) { + + if (value.length < 11) { + LOG.info(" BP Measure started. Waiting for result"); + } else { + LOG.info(" Received BP live data"); + int high = Conversion.fromByteArr16(value[8], value[9]); + int low = Conversion.fromByteArr16(value[10], value[11]); + int timestamp = Conversion.fromByteArr16(value[12], value[13], value[14], value[15]); + + LOG.info(" Calculated BP data: timestamp: " + timestamp + ", high: " + high + ", low: " + low); } } @@ -684,14 +829,14 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { TransactionBuilder builder = performInitialized("handleAck"); builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand((byte)0x00)); + buildCommand((byte) 0x00)); builder.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to response to ACK", e); } } -// This is only for ACK response + // This is only for ACK response private byte[] buildCommand(byte action) { byte[] result = new byte[7]; System.arraycopy(WatchXPlusConstants.CMD_HEADER, 0, result, 0, 5); @@ -707,10 +852,103 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { private void handleStepsInfo(byte[] value) { int steps = Conversion.fromByteArr16(value[8], value[9]); - if (LOG.isDebugEnabled()) { - LOG.debug(" Received steps count: " + steps); + LOG.debug(" Received steps count: " + steps); + + // This code is from MakibesHR3DeviceSupport + Calendar date = GregorianCalendar.getInstance(); + int timestamp = (int) (date.getTimeInMillis() / 1000); + + // We need to subtract the day's total step count thus far. + int dayStepCount = this.getStepsOnDay(timestamp); + + int newSteps = (steps - dayStepCount); + + if (newSteps > 0) { + LOG.debug("adding " + newSteps + " steps"); + + try (DBHandler dbHandler = GBApplication.acquireDB()) { + WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(getDevice(), dbHandler.getDaoSession()); + + WatchXPlusActivitySample sample = createSample(dbHandler, timestamp); + sample.setTimestamp(timestamp); +// sample.setRawKind(record.type); + sample.setSteps(newSteps); +// sample.setDistance(record.distance); +// sample.setCalories(record.calories); +// sample.setDistance(record.distance); +// sample.setHeartRate((record.maxHeartRate - record.minHeartRate) / 2); //TODO: Find an alternative approach for Day Summary Heart Rate +// sample.setRawHPlusHealthData(record.getRawData()); + + sample.setProvider(provider); + provider.addGBActivitySample(sample); + } catch (GBException ex) { + LOG.info((ex.getMessage())); + } catch (Exception ex) { + LOG.info(ex.getMessage()); + } } -// TODO: save steps to DB + } + + /** + * @param timeStamp Time stamp at some point during the requested day. + */ + private int getStepsOnDay(int timeStamp) { + try (DBHandler dbHandler = GBApplication.acquireDB()) { + + Calendar dayStart = new GregorianCalendar(); + Calendar dayEnd = new GregorianCalendar(); + + this.getDayStartEnd(timeStamp, dayStart, dayEnd); + + WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(this.getDevice(), dbHandler.getDaoSession()); + + List samples = provider.getAllActivitySamples( + (int) (dayStart.getTimeInMillis() / 1000L), + (int) (dayEnd.getTimeInMillis() / 1000L)); + + int totalSteps = 0; + + for (WatchXPlusActivitySample sample : samples) { + totalSteps += sample.getSteps(); + } + + return totalSteps; + + } catch (Exception ex) { + LOG.error(ex.getMessage()); + + return 0; + } + } + + /** + * @param timeStamp seconds + */ + private void getDayStartEnd(int timeStamp, Calendar start, Calendar end) { + final int DAY = (24 * 60 * 60); + + int timeStampStart = ((timeStamp / DAY) * DAY); + int timeStampEnd = (timeStampStart + DAY); + + start.setTimeInMillis(timeStampStart * 1000L); + end.setTimeInMillis(timeStampEnd * 1000L); + } + + private WatchXPlusActivitySample createSample(DBHandler dbHandler, int timestamp) { + Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + WatchXPlusActivitySample sample = new WatchXPlusActivitySample( + timestamp, // ts + deviceId, userId, // User id + null, // Raw Data + ActivityKind.TYPE_UNKNOWN, // rawKind + ActivitySample.NOT_MEASURED, // Steps + ActivitySample.NOT_MEASURED, // HR + ActivitySample.NOT_MEASURED, // Distance + ActivitySample.NOT_MEASURED // Calories + ); + + return sample; } private byte[] buildCommand(byte[] command, byte action) { @@ -743,7 +981,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } private void handleFirmwareInfo(byte[] value) { - versionInfo.fwVersion = String.format(Locale.US,"%d.%d.%d", value[8], value[9], value[10]); + versionInfo.fwVersion = String.format(Locale.US, "%d.%d.%d", value[8], value[9], value[10]); handleGBDeviceEvent(versionInfo); } @@ -760,25 +998,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { super.dispose(); } - private static void calculateHeartRateDetails(byte[] bArr) { - - int timestamp = Conversion.fromByteArr16(bArr[8], bArr[9], bArr[10], bArr[11]); - int dataLength = Conversion.fromByteArr16(bArr[12], bArr[13]); - int samplingInterval = (int) onSamplingInterval(bArr[14] >> 4, Conversion.fromByteArr16((byte) (bArr[14] & 15), bArr[15])); - int mtu = Conversion.fromByteArr16(bArr[16]); - int parts = dataLength / 16; - if (dataLength % 16 > 0) { - parts++; - } - - LOG.info("timestamp (UTC): " + timestamp); - LOG.info("timestamp (UTC): " + new Date(timestamp)); - LOG.info("dataLength (data length): " + dataLength); - LOG.info("samplingInterval (per time): " + samplingInterval); - LOG.info("mtu (mtu): " + mtu); - LOG.info("parts: " + parts); - } - private static double onSamplingInterval(int i, int i2) { switch (i) { case 1: @@ -814,6 +1033,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { static byte[] toByteArr16(int value) { return new byte[]{(byte) (value >> 8), (byte) value}; } + static int fromByteArr16(byte... value) { int intValue = 0; for (int i2 = 0; i2 < value.length; i2++) { From 4069021924585b33817cc4b08cf5960318f1c6ee Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Mon, 28 Oct 2019 22:55:34 +0100 Subject: [PATCH 012/546] Retrieve historical HR data from Watch X Plus and save to DB (WIP). --- .../gadgetbridge/devices/lenovo/DataType.java | 31 +++ .../watchxplus/WatchXPlusConstants.java | 12 +- .../lenovo/operations/InitOperation.java | 6 +- .../watchxplus/WatchXPlusDeviceSupport.java | 229 +++++++++++------- 4 files changed, 190 insertions(+), 88 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/DataType.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/DataType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/DataType.java new file mode 100644 index 000000000..ce4d4cc73 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/DataType.java @@ -0,0 +1,31 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.lenovo; + +public enum DataType { + STEPS(new byte[]{0x00, 0x00}), + SLEEP(new byte[]{0x00, 0x01}), + HEART_RATE(new byte[]{0x00, 0x02}), + BLOOD_PRESSURE(new byte[]{0x00, 0x06}), + INFRARED_TEMPERATURE(new byte[]{0x00, 0x08}), + ENVIRONMENT_TEMPERATURE(new byte[]{0x00, 0x09}), + AIR_PRESSURE(new byte[]{0x00, 0x0A}); + + private byte[] value; + + DataType(byte[] value) { + this.value = value; + } + + public byte[] getValue() { + return value; + } + + public static DataType getType(int value) { + for(DataType type : values()) { + int intVal = (type.getValue()[1] & 0xff) | ((type.getValue()[0] & 0xff) << 8); + if(intVal == value) { + return type; + } + } + throw new RuntimeException("No value defined for " + value); + } +} 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 4bf09b94d..0a08fb10d 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 @@ -26,7 +26,7 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final UUID UUID_UNKNOWN_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); public static final UUID UUID_CHARACTERISTIC_WRITE = UUID.fromString("0000a801-0000-1000-8000-00805f9b34fb"); - public static final UUID UUID_CHARACTERISTIC_UNKNOWN_2 = UUID.fromString("0000a802-0000-1000-8000-00805f9b34fb"); + public static final UUID UUID_CHARACTERISTIC_DATABASE_READ = UUID.fromString("0000a802-0000-1000-8000-00805f9b34fb"); public static final UUID UUID_CHARACTERISTIC_UNKNOWN_3 = UUID.fromString("0000a803-0000-1000-8000-00805f9b34fb"); public static final UUID UUID_CHARACTERISTIC_UNKNOWN_4 = UUID.fromString("0000a804-0000-1000-8000-00805f9b34fb"); @@ -37,9 +37,10 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] CMD_RETRIEVE_DATA_COUNT = new byte[]{(byte)0xF0, 0x10}; public static final byte[] CMD_RETRIEVE_DATA_DETAILS = new byte[]{(byte)0xF0, 0x11}; public static final byte[] CMD_RETRIEVE_DATA_CONTENT = new byte[]{(byte)0xF0, 0x12}; - public static final byte[] HEART_RATE_DATA_TYPE = new byte[]{0x00, 0x02}; + 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_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06}; public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02}; public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61}; @@ -51,9 +52,10 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] RESP_DAY_STEPS_INDICATOR = new byte[]{0x08, 0x10, 0x03}; public static final byte[] RESP_HEARTRATE = new byte[]{-0x80, 0x15, 0x03}; - public static final byte[] RESP_HEART_RATE_DATA_COUNT = new byte[]{0x08, (byte)0xF0, 0x10}; - public static final byte[] RESP_HEART_RATE_DATA_DETAILS = new byte[]{0x08, (byte)0xF0, 0x11}; - public static final byte[] RESP_HEART_RATE_DATA_CONTENT = new byte[]{0x08, (byte)0xF0, 0x12}; + public static final byte[] RESP_DATA_COUNT = new byte[]{0x08, (byte)0xF0, 0x10}; + public static final byte[] RESP_DATA_DETAILS = new byte[]{0x08, (byte)0xF0, 0x11}; + public static final byte[] RESP_DATA_CONTENT = new byte[]{0x08, (byte)0xF0, 0x12}; + public static final byte[] RESP_DATA_CONTENT_REMOVE = new byte[]{-0x80, (byte)0xF0, 0x32}; public static final byte[] RESP_BP_MEASURE_STARTED = new byte[]{0x08, 0x05, 0x0D}; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java index c83cadbee..c161e7f50 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.UUID; +import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants; import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9Constants; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation; @@ -41,7 +42,8 @@ public class InitOperation extends AbstractBTLEOperation heartRateDataToFetch = new ArrayList<>(); - private int requestedHeartRateTimestamp; - private int heartRateDataSlots; + private Map dataToFetch = new LinkedHashMap<>(); + private int requestedDataTimestamp; + private Map dataSlots = new HashMap<>(); + private DataType currentDataType; private byte ACK_CALIBRATION = 0; @@ -502,7 +507,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { WatchXPlusConstants.READ_VALUE)); // Fetch heart rate data samples count - requestHeartRateDataCount(builder); + requestDataCount(builder, DataType.HEART_RATE); builder.queue(getQueue()); } catch (IOException e) { @@ -631,11 +636,9 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { super.onCharacteristicChanged(gatt, characteristic); UUID characteristicUUID = characteristic.getUuid(); + byte[] value = characteristic.getValue(); if (WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE.equals(characteristicUUID)) { - byte[] value = characteristic.getValue(); - if (value[0] != 0x23) { - handleHeartRateContentDataChunk(value); - } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_FIRMWARE_INFO, 5)) { + if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_FIRMWARE_INFO, 5)) { handleFirmwareInfo(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BATTERY_INFO, 5)) { handleBatteryState(value); @@ -651,17 +654,19 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { isCalibrationActive = false; } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DAY_STEPS_INDICATOR, 5)) { handleStepsInfo(value); - } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_COUNT, 5)) { - LOG.info(" Received Heart rate data count"); - handleHeartRateDataCount(value); - } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_DETAILS, 5)) { - LOG.info(" Received Heart rate data details"); - handleHeartRateDetails(value); - } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_HEART_RATE_DATA_CONTENT, 5)) { - LOG.info(" Received Heart rate data content"); - handleHeartRateContentAck(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DATA_COUNT, 5)) { + LOG.info(" Received data count"); + handleDataCount(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DATA_DETAILS, 5)) { + LOG.info(" Received data details"); + handleDataDetails(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DATA_CONTENT, 5)) { + LOG.info(" Received data content"); + handleDataContentAck(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BP_MEASURE_STARTED, 5)) { handleBpMeasureResult(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_DATA_CONTENT_REMOVE, 5)) { + handleDataContentRemove(value); } else if (value.length == 7 && value[5] == 0) { LOG.info(" Received ACK"); // Not sure if that's necessary. There is no response for ACK in original app logs @@ -673,6 +678,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { logMessageContent(characteristic.getValue()); } + return true; + } else if (WatchXPlusConstants.UUID_CHARACTERISTIC_DATABASE_READ.equals(characteristicUUID)) { + + handleContentDataChunk(value); return true; } else { LOG.info(" Unhandled characteristic changed: " + characteristicUUID); @@ -682,44 +691,59 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return false; } - /** - * Heart rate history retrieve flow: - * 1. Request for heart rate data slots count. CMD_RETRIEVE_DATA_COUNT, {@link WatchXPlusDeviceSupport#requestHeartRateDataCount} - * 2. Extract data count from response. RESP_HEART_RATE_DATA_COUNT, {@link WatchXPlusDeviceSupport#handleHeartRateDataCount} - * 3. Request for N data slot details. CMD_RETRIEVE_DATA_DETAILS, {@link WatchXPlusDeviceSupport#requestHeartRateDetails} - * 4. Timestamp of slot is returned, save it for later use. RESP_HEART_RATE_DATA_DETAILS, {@link WatchXPlusDeviceSupport#handleHeartRateDetails} - * 5. Repeat step 3-4 until all slots details retrieved. - * 6. Request for M data content by timestamp. CMD_RETRIEVE_DATA_CONTENT, {@link WatchXPlusDeviceSupport#requestHeartRateContentForTimestamp} - * 7. Receive kind of pre-flight response. RESP_HEART_RATE_DATA_CONTENT, {@link WatchXPlusDeviceSupport#handleHeartRateContentAck} - * 8. Receive frames with content. They are different than other frames, {@link WatchXPlusDeviceSupport#handleHeartRateContentDataChunk} - * ie. 0000000255-4F4C48-434241434444454648474747, 0001000247-474645-434240FFFFFFFFFFFFFFFFFF - */ - private void requestHeartRateDataCount(TransactionBuilder builder) { - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_COUNT, - WatchXPlusConstants.READ_VALUE, - WatchXPlusConstants.HEART_RATE_DATA_TYPE)); - } - - private void handleHeartRateDataCount(byte[] value) { - - int dataCount = Conversion.fromByteArr16(value[10], value[11]); - LOG.info("Watch contains " + dataCount + " heart rate entries"); - this.heartRateDataSlots = dataCount; - heartRateDataToFetch.clear(); - if (dataCount != 0) { - requestHeartRateDetails(heartRateDataToFetch.size()); + private void handleDataContentRemove(byte[] value) { + int dataType = Conversion.fromByteArr16(value[8], value[9]); + int timestamp = Conversion.fromByteArr16(value[10], value[11], value[12], value[13]); + int removed = value[14]; + DataType type = DataType.getType(dataType); + if( removed == 0) { + LOG.info(" Removed " + type + " data for timestamp " + timestamp); + } else { + LOG.info(" Unsuccessful removal of " + type + " data for timestamp " + timestamp); } } - private void requestHeartRateDetails(int i) { + /** + * Heart rate history retrieve flow: + * 1. Request for heart rate data slots count. CMD_RETRIEVE_DATA_COUNT, {@link WatchXPlusDeviceSupport#requestDataCount} + * 2. Extract data count from response. RESP_DATA_COUNT, {@link WatchXPlusDeviceSupport#handleDataCount} + * 3. Request for N data slot details. CMD_RETRIEVE_DATA_DETAILS, {@link WatchXPlusDeviceSupport#requestDataDetails} + * 4. Timestamp of slot is returned, save it for later use. RESP_DATA_DETAILS, {@link WatchXPlusDeviceSupport#handleDataDetails} + * 5. Repeat step 3-4 until all slots details retrieved. + * 6. Request for M data content by timestamp. CMD_RETRIEVE_DATA_CONTENT, {@link WatchXPlusDeviceSupport#requestDataContentForTimestamp} + * 7. Receive kind of pre-flight response. RESP_DATA_CONTENT, {@link WatchXPlusDeviceSupport#handleDataContentAck} + * 8. Receive frames with content. They are different than other frames, {@link WatchXPlusDeviceSupport#handleContentDataChunk} + * ie. 0000000255-4F4C48-434241434444454648474747, 0001000247-474645-434240FFFFFFFFFFFFFFFFFF + */ + private void requestDataCount(TransactionBuilder builder, DataType dataType) { + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_COUNT, + WatchXPlusConstants.READ_VALUE, + dataType.getValue())); + } + + private void handleDataCount(byte[] value) { + + int dataType = Conversion.fromByteArr16(value[8], value[9]); + int dataCount = Conversion.fromByteArr16(value[10], value[11]); + + DataType type = DataType.getType(dataType); + LOG.info("Watch contains " + dataCount + " " + type + " entries"); + dataSlots.put(type, dataCount); + dataToFetch.clear(); + if (dataCount != 0) { + requestDataDetails(dataToFetch.size(), type); + } + } + + private void requestDataDetails(int i, DataType dataType) { + LOG.info(" Requesting " + dataType + " details"); try { - TransactionBuilder builder = performInitialized("requestHeartRate"); - byte[] heartRateDataType = WatchXPlusConstants.HEART_RATE_DATA_TYPE; + TransactionBuilder builder = performInitialized("requestDataDetails"); byte[] index = Conversion.toByteArr16(i); - byte[] req = BLETypeConversions.join(heartRateDataType, index); - + byte[] req = BLETypeConversions.join(dataType.getValue(), index); + currentDataType = dataType; builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_DETAILS, WatchXPlusConstants.READ_VALUE, @@ -727,12 +751,12 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { builder.queue(getQueue()); } catch (IOException e) { - LOG.warn("Unable to request data", e); + LOG.warn("Unable to request data details", e); } } - private void handleHeartRateDetails(byte[] value) { - LOG.info("Got Heart rate details"); + private void handleDataDetails(byte[] value) { + LOG.info("Got data details"); int timestamp = Conversion.fromByteArr16(value[8], value[9], value[10], value[11]); int dataLength = Conversion.fromByteArr16(value[12], value[13]); int samplingInterval = (int) onSamplingInterval(value[14] >> 4, Conversion.fromByteArr16((byte) (value[14] & 15), value[15])); @@ -749,63 +773,105 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { LOG.info("mtu (mtu): " + mtu); LOG.info("parts: " + parts); - heartRateDataToFetch.add(timestamp); + dataToFetch.put(timestamp, parts); - if (heartRateDataToFetch.size() == heartRateDataSlots) { - requestHeartRateContentForTimestamp(heartRateDataToFetch.get(0)); + if (dataToFetch.size() == dataSlots.get(currentDataType)) { + Map.Entry currentValue = dataToFetch.entrySet().iterator().next(); + requestedDataTimestamp = currentValue.getKey(); + requestDataContentForTimestamp(requestedDataTimestamp, currentDataType); } else { - requestHeartRateDetails(heartRateDataToFetch.size()); + requestDataDetails(dataToFetch.size(), currentDataType); } } - private void requestHeartRateContentForTimestamp(int timestamp) { - byte[] heartRateDataType = WatchXPlusConstants.HEART_RATE_DATA_TYPE; + private void requestDataContentForTimestamp(int timestamp, DataType dataType) { byte[] command = WatchXPlusConstants.CMD_RETRIEVE_DATA_CONTENT; try { - TransactionBuilder builder = performInitialized("content"); + TransactionBuilder builder = performInitialized("requestDataContentForTimestamp"); byte[] ts = Conversion.toByteArr32(timestamp); - byte[] req = BLETypeConversions.join(heartRateDataType, ts); + byte[] req = BLETypeConversions.join(dataType.getValue(), ts); req = BLETypeConversions.join(req, Conversion.toByteArr16(0)); - requestedHeartRateTimestamp = timestamp; + requestedDataTimestamp = timestamp; builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(command, WatchXPlusConstants.READ_VALUE, req)); builder.queue(getQueue()); } catch (IOException e) { - LOG.warn("Unable to request heart rate content", e); + LOG.warn("Unable to request data content", e); } } + private void removeDataContentForTimestamp(int timestamp, DataType dataType) { + byte[] command = WatchXPlusConstants.CMD_REMOVE_DATA_CONTENT; - private void handleHeartRateContentAck(byte[] value) { - LOG.info(" Received heart rate data content start"); + try { + TransactionBuilder builder = performInitialized("removeDataContentForTimestamp"); + byte[] ts = Conversion.toByteArr32(timestamp); + byte[] req = BLETypeConversions.join(dataType.getValue(), ts); + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.TASK, + req)); + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("Unable to remove data content", e); + } } - private void handleHeartRateContentDataChunk(byte[] value) { + private void handleDataContentAck(byte[] value) { + LOG.info(" Received data content start"); +// To verify: Chunks are sent if value[8] == 0, if value[8] == 1 they are not sent by watch + } + + private void handleContentDataChunk(byte[] value) { int chunkNo = Conversion.fromByteArr16(value[0], value[1]); - int dataType = Conversion.fromByteArr16(value[2], value[2]); - int timezoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis()); - if (dataType != 2) { - LOG.warn(" Got unsupported data package type: " + dataType); - } else { - for (int i = 4; i < value.length; i++) { + int dataType = Conversion.fromByteArr16(value[2], value[3]); + int timezoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis())/1000; + DataType type = DataType.getType(dataType); + if (type == DataType.HEART_RATE) { + try (DBHandler dbHandler = GBApplication.acquireDB()) { + WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(getDevice(), dbHandler.getDaoSession()); + List samples = new ArrayList<>(); - int val = Conversion.fromByteArr16(value[i]); - if (255 == val) { - break; + for (int i = 4; i < value.length; i++) { + + int val = Conversion.fromByteArr16(value[i]); + if (255 == val) { + break; + } + int tsWithOffset = requestedDataTimestamp + (((((chunkNo * 16) + i) - 4) * 2) * 60) - timezoneOffset; +// LOG.debug(" requested timestamp " + requestedDataTimestamp + " chunkNo " + chunkNo + " Got data: " + new Date((long) tsWithOffset * 1000) + ", value: " + val); + WatchXPlusActivitySample sample = createSample(dbHandler, tsWithOffset); + sample.setTimestamp(tsWithOffset); + sample.setHeartRate(val); + sample.setProvider(provider); + sample.setRawKind(ActivityKind.TYPE_ACTIVITY); + samples.add(sample); } - int tsWithOffset = requestedHeartRateTimestamp + (((((chunkNo * 16) + i) - 4) * 2) * 60) - timezoneOffset; - LOG.info(" Got HR data: " + new Date(tsWithOffset) + ", value: " + val); + provider.addGBActivitySamples(samples.toArray(new WatchXPlusActivitySample[0])); + } catch (GBException ex) { + LOG.info((ex.getMessage())); + } catch (Exception ex) { + LOG.info(ex.getMessage()); } - heartRateDataToFetch.remove(0); - if (!heartRateDataToFetch.isEmpty()) { - requestHeartRateContentForTimestamp(heartRateDataToFetch.get(0)); - } else { - heartRateDataSlots = 0; + if(!dataToFetch.isEmpty() && chunkNo == dataToFetch.get(requestedDataTimestamp) - 1) { + dataToFetch.remove(requestedDataTimestamp); + removeDataContentForTimestamp(requestedDataTimestamp, currentDataType); + if (!dataToFetch.isEmpty()) { + Map.Entry currentValue = dataToFetch.entrySet().iterator().next(); + requestedDataTimestamp = currentValue.getKey(); + requestDataContentForTimestamp(requestedDataTimestamp, type); + } else { + dataSlots.put(type,0); + } + } else if (dataToFetch.isEmpty()) { + dataSlots.put(type,0); } + } else { + LOG.warn(" Got unsupported data package type: " + type); } } @@ -872,6 +938,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { WatchXPlusActivitySample sample = createSample(dbHandler, timestamp); sample.setTimestamp(timestamp); // sample.setRawKind(record.type); + sample.setRawKind(ActivityKind.TYPE_ACTIVITY); sample.setSteps(newSteps); // sample.setDistance(record.distance); // sample.setCalories(record.calories); From 8f9466ee1c2aa216d0d72581991692faf52d574d Mon Sep 17 00:00:00 2001 From: mkusnierz <> Date: Tue, 29 Oct 2019 22:34:31 +0100 Subject: [PATCH 013/546] Initial support for sleep data retrieval --- .../gadgetbridge/daogen/GBDaoGenerator.java | 19 ++- .../watchxplus/WatchXPlusDeviceSupport.java | 117 +++++++++++++----- 2 files changed, 102 insertions(+), 34 deletions(-) diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index ac073ec7e..a8ab19315 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -73,6 +73,7 @@ public class GBDaoGenerator { addZeTimeActivitySample(schema, user, device); addID115ActivitySample(schema, user, device); addWatchXPlusHealthActivitySample(schema, user, device); + addWatchXPlusHealthActivityKindOverlay(schema, user, device); addCalendarSyncState(schema, device); addAlarms(schema, user, device); @@ -337,7 +338,7 @@ public class GBDaoGenerator { addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device); activitySample.addByteArrayProperty("rawWatchXPlusHealthData"); activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey(); -// activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE); activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE); addHeartRateProperties(activitySample); activitySample.addIntProperty("distance"); @@ -345,6 +346,22 @@ public class GBDaoGenerator { return activitySample; } + private static Entity addWatchXPlusHealthActivityKindOverlay(Schema schema, Entity user, Entity device) { + Entity activityOverlay = addEntity(schema, "WatchXPlusHealthActivityOverlay"); + + activityOverlay.addIntProperty(TIMESTAMP_FROM).notNull().primaryKey(); + activityOverlay.addIntProperty(TIMESTAMP_TO).notNull().primaryKey(); + activityOverlay.addIntProperty(SAMPLE_RAW_KIND).notNull().primaryKey(); + Property deviceId = activityOverlay.addLongProperty("deviceId").primaryKey().notNull().getProperty(); + activityOverlay.addToOne(device, deviceId); + + Property userId = activityOverlay.addLongProperty("userId").notNull().getProperty(); + activityOverlay.addToOne(user, userId); + activityOverlay.addByteArrayProperty("rawWatchXPlusHealthData"); + + return activityOverlay; + } + private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) { activitySample.setSuperclass(superClass); activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider"); 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 823a43a09..926fea157 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 @@ -37,7 +37,6 @@ import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -55,6 +54,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.DataType; import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants; import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusSampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusHealthActivityOverlay; +import nodomain.freeyourgadget.gadgetbridge.entities.WatchXPlusHealthActivityOverlayDao; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; @@ -86,7 +87,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { private Map dataToFetch = new LinkedHashMap<>(); private int requestedDataTimestamp; - private Map dataSlots = new HashMap<>(); + private int dataSlots = 0; private DataType currentDataType; private byte ACK_CALIBRATION = 0; @@ -507,7 +508,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { WatchXPlusConstants.READ_VALUE)); // Fetch heart rate data samples count - requestDataCount(builder, DataType.HEART_RATE); + requestDataCount(DataType.HEART_RATE); builder.queue(getQueue()); } catch (IOException e) { @@ -715,11 +716,21 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { * 8. Receive frames with content. They are different than other frames, {@link WatchXPlusDeviceSupport#handleContentDataChunk} * ie. 0000000255-4F4C48-434241434444454648474747, 0001000247-474645-434240FFFFFFFFFFFFFFFFFF */ - private void requestDataCount(TransactionBuilder builder, DataType dataType) { - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_COUNT, - WatchXPlusConstants.READ_VALUE, - dataType.getValue())); + private void requestDataCount(DataType dataType) { + + TransactionBuilder builder; + try { + builder = performInitialized("requestDataCount"); + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_RETRIEVE_DATA_COUNT, + WatchXPlusConstants.READ_VALUE, + dataType.getValue())); + + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("Unable to send request to retrieve recorded data", e); + } } private void handleDataCount(byte[] value) { @@ -729,7 +740,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { DataType type = DataType.getType(dataType); LOG.info("Watch contains " + dataCount + " " + type + " entries"); - dataSlots.put(type, dataCount); + dataSlots = dataCount; dataToFetch.clear(); if (dataCount != 0) { requestDataDetails(dataToFetch.size(), type); @@ -775,7 +786,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { dataToFetch.put(timestamp, parts); - if (dataToFetch.size() == dataSlots.get(currentDataType)) { + if (dataToFetch.size() == dataSlots) { Map.Entry currentValue = dataToFetch.entrySet().iterator().next(); requestedDataTimestamp = currentValue.getKey(); requestDataContentForTimestamp(requestedDataTimestamp, currentDataType); @@ -830,10 +841,36 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { int dataType = Conversion.fromByteArr16(value[2], value[3]); int timezoneOffset = TimeZone.getDefault().getOffset(System.currentTimeMillis())/1000; DataType type = DataType.getType(dataType); - if (type == DataType.HEART_RATE) { - try (DBHandler dbHandler = GBApplication.acquireDB()) { - WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(getDevice(), dbHandler.getDaoSession()); - List samples = new ArrayList<>(); + try (DBHandler dbHandler = GBApplication.acquireDB()) { + WatchXPlusSampleProvider provider = new WatchXPlusSampleProvider(getDevice(), dbHandler.getDaoSession()); + List samples = new ArrayList<>(); + + if (DataType.SLEEP.equals(type)) { + WatchXPlusHealthActivityOverlayDao overlayDao = dbHandler.getDaoSession().getWatchXPlusHealthActivityOverlayDao(); + List overlayList = new ArrayList<>(); + + for (int i = 4; i < value.length; i+= 2) { + + int val = Conversion.fromByteArr16(value[i], value[i+1]); + if (65535 == val) { + break; + } + + int tsWithOffset = requestedDataTimestamp + (((((chunkNo * 16) / 2) + ((i - 4) / 2)) *5) * 60) - timezoneOffset; + LOG.debug(" requested timestamp " + requestedDataTimestamp + " chunkNo " + chunkNo + " Got data: " + new Date((long) tsWithOffset * 1000) + ", value: " + val); + WatchXPlusActivitySample sample = createSample(dbHandler, tsWithOffset); + sample.setTimestamp(tsWithOffset); + sample.setProvider(provider); + sample.setRawIntensity(val); + sample.setRawKind(val == 0 ? ActivityKind.TYPE_DEEP_SLEEP : ActivityKind.TYPE_LIGHT_SLEEP); + samples.add(sample); + overlayList.add(new WatchXPlusHealthActivityOverlay(sample.getTimestamp(), sample.getTimestamp()+300, sample.getRawKind(), sample.getDeviceId(), sample.getUserId(), sample.getRawWatchXPlusHealthData())); + } + overlayDao.insertOrReplaceInTx(overlayList); + provider.addGBActivitySamples(samples.toArray(new WatchXPlusActivitySample[0])); + + handleEndOfDataChunks(chunkNo, type); + } else if (DataType.HEART_RATE.equals(type)) { for (int i = 4; i < value.length; i++) { @@ -851,27 +888,40 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { samples.add(sample); } provider.addGBActivitySamples(samples.toArray(new WatchXPlusActivitySample[0])); - } catch (GBException ex) { - LOG.info((ex.getMessage())); - } catch (Exception ex) { - LOG.info(ex.getMessage()); - } - if(!dataToFetch.isEmpty() && chunkNo == dataToFetch.get(requestedDataTimestamp) - 1) { - dataToFetch.remove(requestedDataTimestamp); - removeDataContentForTimestamp(requestedDataTimestamp, currentDataType); - if (!dataToFetch.isEmpty()) { - Map.Entry currentValue = dataToFetch.entrySet().iterator().next(); - requestedDataTimestamp = currentValue.getKey(); - requestDataContentForTimestamp(requestedDataTimestamp, type); - } else { - dataSlots.put(type,0); - } - } else if (dataToFetch.isEmpty()) { - dataSlots.put(type,0); + handleEndOfDataChunks(chunkNo, type); + } else { + LOG.warn(" Got unsupported data package type: " + type); + } + } catch (GBException ex) { + LOG.info((ex.getMessage())); + } catch (Exception ex) { + LOG.info(ex.getMessage()); + } + + } + + private void handleEndOfDataChunks(int chunkNo, DataType type) { + if(!dataToFetch.isEmpty() && chunkNo == dataToFetch.get(requestedDataTimestamp) - 1) { + dataToFetch.remove(requestedDataTimestamp); + removeDataContentForTimestamp(requestedDataTimestamp, currentDataType); + if (!dataToFetch.isEmpty()) { + Map.Entry currentValue = dataToFetch.entrySet().iterator().next(); + requestedDataTimestamp = currentValue.getKey(); + requestDataContentForTimestamp(requestedDataTimestamp, type); + } else { + dataSlots = 0; + if(type.equals(DataType.HEART_RATE)) { + currentDataType = DataType.SLEEP; + requestDataCount(currentDataType); + } + } + } else if (dataToFetch.isEmpty()) { + dataSlots = 0; + if(type.equals(DataType.HEART_RATE)) { + currentDataType = DataType.SLEEP; + requestDataCount(currentDataType); } - } else { - LOG.warn(" Got unsupported data package type: " + type); } } @@ -1009,6 +1059,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { deviceId, userId, // User id null, // Raw Data ActivityKind.TYPE_UNKNOWN, // rawKind + ActivitySample.NOT_MEASURED, // rawIntensity ActivitySample.NOT_MEASURED, // Steps ActivitySample.NOT_MEASURED, // HR ActivitySample.NOT_MEASURED, // Distance From fb08af6d045231756cddaea1938626fee026656c Mon Sep 17 00:00:00 2001 From: mamutcho Date: Sat, 2 Nov 2019 22:24:00 +0200 Subject: [PATCH 014/546] Add Device settings (screen on, time format, disconnect reminder, find my phone). Changed device icon. --- app/src/main/AndroidManifest.xml | 2 +- .../LenovoWatchCalibrationActivity.java | 2 +- .../watchxplus/WatchXPlusConstants.java | 15 ++ .../WatchXPlusDeviceCoordinator.java | 75 +++++++- .../gadgetbridge/model/DeviceType.java | 4 +- .../watchxplus/WatchXPlusDeviceSupport.java | 178 +++++++++++++++++- app/src/main/res/values-bg/strings.xml | 14 ++ app/src/main/res/values/strings.xml | 7 + app/src/main/res/xml/preferences.xml | 23 +++ 9 files changed, 309 insertions(+), 11 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e1e1d26f9..bf0a9c242 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -411,7 +411,7 @@ android:label="@string/title_activity_watch9_pairing" /> + android:label="@string/title_activity_watchXplus_calibration" /> > 1) & 1) != 1) { + //z = false; + refuse = refuse + " off"; + } else { + refuse = refuse + " on"; + } + LOG.info(" handleShakeState: " + light + " " + refuse); + } + +// handle disconnect reminder state +// for test purposes only + private void handleDisconnectReminderState(byte[] value) { + boolean z = true; + if (1 != value[8]) { + z = false; + } + LOG.info(" disconnectReminder: " + Boolean.valueOf(z) + " val: " + value[8]); + + return; + + + } + +// read preferences + private void syncPreferences(TransactionBuilder transaction) { + SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()); + this.setHeadsUpScreen(transaction, sharedPreferences); + this.setDisconnectReminder(transaction, sharedPreferences); + this.setTimeMode(transaction, sharedPreferences); + } + private Handler mFindPhoneHandler = new Handler(); + + private void onReverseFindDevice(boolean start) { + if (start) { + SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs( + this.getDevice().getAddress()); + + int findPhone = WatchXPlusDeviceCoordinator.getFindPhone(sharedPreferences); + + if (findPhone != WatchXPlusDeviceCoordinator.FindPhone_OFF) { + GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone(); + + findPhoneEvent.event = GBDeviceEventFindPhone.Event.START; + + evaluateGBDeviceEvent(findPhoneEvent); + + if (findPhone > 0) { + this.mFindPhoneHandler.postDelayed(new Runnable() { + @Override + public void run() { + onReverseFindDevice(false); + } + }, findPhone * 1000); + } + } + } else { + // Always send stop, ignore preferences. + GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone(); + + findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP; + + evaluateGBDeviceEvent(findPhoneEvent); + } + } + // Set Lift Wrist to Light Screen based on saved preferences + private WatchXPlusDeviceSupport setHeadsUpScreen(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { + return this.setHeadsUpScreen(transactionBuilder, + WatchXPlusDeviceCoordinator.shouldEnableHeadsUpScreen(sharedPreferences)); + } + + // Command to toggle Lift Wrist to Light Screen + private WatchXPlusDeviceSupport setHeadsUpScreen(TransactionBuilder transactionBuilder, boolean enable) { + byte refuseCall = 0x00; + byte lightScreen = 0x00; + if (enable) { + lightScreen = 0x01; + } + byte b = (byte) (lightScreen + (refuseCall << 1)); + byte[] liftScreen = new byte[4]; + liftScreen[0] = 0x00; + liftScreen[1] = 0x00; + liftScreen[2] = 0x00; + liftScreen[3] = b; //byte[11] + transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_SHAKE_SWITCH, + WatchXPlusConstants.WRITE_VALUE, + liftScreen)); + return this; + } + + private WatchXPlusDeviceSupport setDisconnectReminder(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { + return this.setDisconnectReminder(transactionBuilder, + WatchXPlusDeviceCoordinator.shouldEnableDisconnectReminder(sharedPreferences)); + } + + private WatchXPlusDeviceSupport setDisconnectReminder(TransactionBuilder transactionBuilder, boolean enable) { + transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_DISCONNECT_REMIND, + WatchXPlusConstants.WRITE_VALUE, + new byte[]{(byte) (enable ? 0x01 : 0x00)})); + return this; + } + +// Request status of Disconnect reminder + public WatchXPlusDeviceSupport getDisconnectReminderStatus(TransactionBuilder transactionBuilder) { + transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_DISCONNECT_REMIND, + WatchXPlusConstants.READ_VALUE)); + return this; + } +// Request status of Lift Wrist to Light Screen, and Shake to Refuse Call + public WatchXPlusDeviceSupport getShakeStatus(TransactionBuilder transactionBuilder) { + transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_SHAKE_SWITCH, + WatchXPlusConstants.READ_VALUE)); + return this; + } + +// set time format + private WatchXPlusDeviceSupport setTimeMode(TransactionBuilder transactionBuilder, byte timeMode) { + byte[] bArr = new byte[2]; + bArr[0] = 0x01; //byte[08] language - force to English language + bArr[1] = timeMode; //byte[09] time + transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_TIME_LANGUAGE, + WatchXPlusConstants.WRITE_VALUE, + bArr)); + LOG.info(" setTimeMode: " + bArr); + return this; + + } + + private WatchXPlusDeviceSupport setTimeMode(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { + return this.setTimeMode(transactionBuilder, + WatchXPlusDeviceCoordinator.getTimeMode(sharedPreferences)); + } + @Override public void dispose() { LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index b519a3802..8f44a7130 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -30,6 +30,12 @@ Приложения в кеша Инсталирани приложения Инсталирани циферблати + Включи екрана при вдигане + Известие при прекъсване + Намери телефона + Включи намиране на телефона + Използвайте устройството, за да накарате телефона да звъни. + Звънене - секунди Изтриване Изтриване и премахване от кеша Преинсталиране @@ -253,6 +259,14 @@ Име/Псевдоним Брой на вибрациите Когато часовникът завибрира, разклатете устройството или натиснете бутона + Минути: + Часове: + Секунди: + Задайте времето, което показва устройството Ви. + Сверяване + Watch 9 свързване + Watch 9 сверяване + WatchX Plus сверяване Наблюдение/анализ на съня Съхраняване на log файлове Инициализиране diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 748a27d6b..2149d785e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -186,6 +186,12 @@ Screen on duration All day heart rate measurement HPlus/Makibes settings + + Units + Time format + Screen on duration + Lift wirst to screen on + WatchXPlus settings Makibes HR3 settings @@ -701,6 +707,7 @@ Calibrate Watch 9 pairing Watch 9 calibration + WatchX Plus calibration Contextual Arabic Enable this to support contextual Arabic Right To Left Support diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index a4d9ac89f..706c633d9 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -574,6 +574,29 @@ android:icon="@drawable/ic_device_zetime" android:key="pref_key_zetime" android:title="@string/zetime_title_settings"/> + + + + + + + + + + + Date: Mon, 4 Nov 2019 00:41:48 +0200 Subject: [PATCH 015/546] Add Device settings - altitude Add repeat notification on phone call - need to refine Add command for cancel text notification --- .../watchxplus/WatchXPlusConstants.java | 5 + .../WatchXPlusDeviceCoordinator.java | 15 +++ .../lenovo/operations/InitOperation.java | 2 +- .../watchxplus/WatchXPlusDeviceSupport.java | 99 ++++++++++++++++--- app/src/main/res/values-bg/strings.xml | 17 ++++ app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/preferences.xml | 15 ++- 7 files changed, 130 insertions(+), 27 deletions(-) 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 3a42bdbf8..2ac346f40 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 @@ -34,6 +34,9 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final String PREF_DISCONNECT_REMIND = "disconnect_notification"; public static final String PREF_FIND_PHONE = "prefs_find_phone"; 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"; + // time format constants public static final byte ARG_SET_TIMEMODE_24H = 0x00; public static final byte ARG_SET_TIMEMODE_12H = 0x01; @@ -50,6 +53,7 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] CMD_NOTIFICATION_TEXT_TASK = new byte[]{0x03, 0x06}; + public static final byte[] CMD_NOTIFICATION_CANCEL = new byte[]{0x03, 0x04}; public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02}; public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61}; @@ -59,6 +63,7 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] CMD_SHAKE_SWITCH = new byte[]{0x03, -0x6E}; public static final byte[] CMD_DISCONNECT_REMIND = new byte[]{0x00, 0x11}; public static final byte[] CMD_TIME_LANGUAGE = new byte[]{0x03, -0x6F}; + public static final byte[] CMD_ALTITUDE = new byte[]{0x05, 0x0A}; public static final byte[] RESP_SHAKE_SWITCH = new byte[]{0x08, 0x03, -0x6E}; public static final byte[] RESP_DISCONNECT_REMIND = new byte[]{0x08, 0x00, 0x11}; 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 f55f8d5ee..2fb9a6179 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 @@ -15,6 +15,7 @@ import androidx.annotation.Nullable; import java.util.Collection; import java.util.Collections; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; @@ -28,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext; @@ -35,6 +37,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { public static final int FindPhone_ON = -1; public static final int FindPhone_OFF = 0; + protected static Prefs prefs = GBApplication.getPrefs(); @NonNull @Override @@ -182,6 +185,18 @@ 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; /** * @return {@link #FindPhone_OFF}, {@link #FindPhone_ON}, or the duration diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java index c161e7f50..f5f366f5b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lenovo/operations/InitOperation.java @@ -79,7 +79,7 @@ public class InitOperation extends AbstractBTLEOperation> 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.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to send notification", e); @@ -439,17 +474,23 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { alarmValue)); } } - + int repeat = 0; @Override public void onSetCallState(CallSpec callSpec) { + SharedPreferences.Editor prefs = GBApplication.getPrefs().getPreferences().edit(); switch (callSpec.command) { case CallSpec.CALL_INCOMING: - sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); + WatchXPlusDeviceCoordinator.getRepeat = 1; + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name, true); break; case CallSpec.CALL_START: + WatchXPlusDeviceCoordinator.getRepeat = 1; + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "stop", false); + break; case CallSpec.CALL_END: -// sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, true); -// break; + WatchXPlusDeviceCoordinator.getRepeat = 0; + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "stop", false); + break; default: break; } @@ -595,6 +636,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { break; case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT: setTimeMode(builder, sharedPreferences); + break; + case WatchXPlusConstants.PREF_ALTITUDE: + LOG.info(" ALTITUDE: " + config); + break; } builder.queue(getQueue()); @@ -1174,10 +1219,12 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { // read preferences private void syncPreferences(TransactionBuilder transaction) { SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()); - this.setHeadsUpScreen(transaction, sharedPreferences); - this.setDisconnectReminder(transaction, sharedPreferences); - this.setTimeMode(transaction, sharedPreferences); + this.setHeadsUpScreen(transaction, sharedPreferences); // lift wirst to screen on + this.setDisconnectReminder(transaction, sharedPreferences); // disconnect reminder + this.setTimeMode(transaction, sharedPreferences); // set time mode 12/24h + this.setAltitude(transaction); // set altitude calibration } + private Handler mFindPhoneHandler = new Handler(); private void onReverseFindDevice(boolean start) { @@ -1220,7 +1267,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { // Command to toggle Lift Wrist to Light Screen private WatchXPlusDeviceSupport setHeadsUpScreen(TransactionBuilder transactionBuilder, boolean enable) { - byte refuseCall = 0x00; + byte refuseCall = 0x00; // force refuse call to OFF byte lightScreen = 0x00; if (enable) { lightScreen = 0x01; @@ -1275,9 +1322,29 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { buildCommand(WatchXPlusConstants.CMD_TIME_LANGUAGE, WatchXPlusConstants.WRITE_VALUE, bArr)); - LOG.info(" setTimeMode: " + bArr); + //LOG.info(" setTimeMode: " + bArr); return this; + } +// calibrate altitude + private WatchXPlusDeviceSupport setAltitude(TransactionBuilder transactionBuilder) { + int value = WatchXPlusDeviceCoordinator.getAltitude(getDevice().getAddress()); + int mAltitude = value; + if (mAltitude < 0) { + mAltitude = (Math.abs(mAltitude) ^ 65535) + 1; + } + int mAirPressure = Math.abs(0); // air pressure 0 ??? + byte[] bArr = new byte[4]; + bArr[0] = (byte) (mAltitude >> 8); // bytr[8] + bArr[1] = (byte) mAltitude; // bytr[9] + bArr[2] = (byte) (mAirPressure >> 8); // bytr[10] + bArr[3] = (byte) mAirPressure; // bytr[11] + transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_ALTITUDE, + WatchXPlusConstants.WRITE_VALUE, + bArr)); + //LOG.info(" setAltitude: " + mAltitude); + return this; } private WatchXPlusDeviceSupport setTimeMode(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 8f44a7130..e3aab489e 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -1,5 +1,22 @@ + Бутон за ново устройство + Винаги видим + Видим само ако няма свързано устройство + Език и регион + За Вас + Година на раждане + Пол + Височина в cm + Тегло в kg + Настройки на Графиката + Показвай средни стойности + Настройки на графика + Max сърдечен ритъм + Min сърдечен ритъм + Обхват на Графиката + Обхвата е Месец + Обхвата е Седмица Gadgetbridge Gadgetbridge Настройки diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2149d785e..ea014997e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -189,8 +189,8 @@ Units Time format - Screen on duration - Lift wirst to screen on + Altitude calibration + Repeat vibration on call WatchXPlus settings Makibes HR3 settings diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 706c633d9..5d1cd46e9 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -584,15 +584,14 @@ android:title="@string/pref_header_general"> + android:defaultValue="200" + android:key="watchxplus_altitude" + android:title="@string/pref_wxp_title_altitude"/> - + From 81997e7725f825f49d610c9c032c24b923d0688e Mon Sep 17 00:00:00 2001 From: mamutcho Date: Wed, 6 Nov 2019 00:14:29 +0200 Subject: [PATCH 016/546] FIXES: - 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 --- .../watchxplus/WatchXPlusConstants.java | 6 +- .../WatchXPlusDeviceCoordinator.java | 47 +++- .../externalevents/NotificationListener.java | 7 +- .../watchxplus/WatchXPlusDeviceSupport.java | 254 ++++++++++++++---- app/src/main/res/values-bg/strings.xml | 7 + app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/preferences.xml | 17 +- 7 files changed, 264 insertions(+), 78 deletions(-) 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"> + + + + - - - + Date: Wed, 6 Nov 2019 23:56:24 +0200 Subject: [PATCH 017/546] FIXES: - Cleaned code for repeat watch vibration during phone ring. - On my samsung phone and Bulgarian language, when call is ended, on watch shows notification with name "Phone" and vibrates until disconnect from app. Finally managed to fix it. ADD: - Add setting for ignore/reject phone call with device button. - Add setting for ignore/reject phone call with shake device. (duplicates button action) P.S. to apply needs to reconnect device. - Add setting for sending Missed call if call is missed. - Add setting for continious vibration while phone ringing. NEED MORE TESTING: - When phone alarm trigger - send notification on watch. Works on samsung phone. Don't have other phones for testing. P.S. To apply altitude calibration, need to disconnect and reconnect watch --- .../watchxplus/WatchXPlusConstants.java | 6 + .../WatchXPlusDeviceCoordinator.java | 37 +++-- .../externalevents/NotificationListener.java | 10 +- .../watchxplus/WatchXPlusDeviceSupport.java | 131 +++++++++--------- app/src/main/res/values-bg/strings.xml | 8 +- app/src/main/res/values/strings.xml | 8 +- app/src/main/res/xml/preferences.xml | 22 +++ 7 files changed, 142 insertions(+), 80 deletions(-) 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"/> + + + + Date: Thu, 7 Nov 2019 11:10:39 +0200 Subject: [PATCH 018/546] Create WorkProgress Work progress and TODO --- .../devices/lenovo/watchxplus/WorkProgress | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress new file mode 100644 index 000000000..8203a2cd9 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress @@ -0,0 +1,83 @@ +TODO + Watch settings + - Set watch modes (energy saving): + - Normal -> the watch work normally + - Power-saving mode -> the app turn off the bluetooth on the watch + - Trad-watch mode -> the watch only works as an analog one + - Set watch language (currently forced to English) + - Set watch units (metric/imperial) + - Implement send User details to watch + - Age, height, gender, etc. + - Implement Do not disturb (on, off, scheduled) + - Implement themperature alarm on watch + - switch, lowTemp, highTemp + - Implement continious blood pressure measurement (on, off, scheduled) + + Add feature to initiate button press event on watch + - Send command to watch + - Get triger on button press + + Schedulers: + - Screen on scheduler (inApp, not supported by watch) + - Disconnect reminder scheduler (inApp, not supported by watch) + - Do not disturb (supported by watch) + - Continious blood pressure measurement (supported by watch) + + Refine send weather to watch + - Send weather icon + + Refine get activity data + - Fix get sleep data + + Measurements + - Blood pressure mesurement + - Add blood pressure calibration (view) + - Add blood pressure calibration function + - Show blood pressure measurement (view) + - Implement heart rate measurement + - Implement themperature measurement + - Implement UV index measurement + + + +WORK PROGRESS + Send notification to watch + - on incomming call + - add function to cancel notification on watch (04.11.2019) + - cancel notification on change phone state (end call, reject call etc.) (06.11.2019) + - settings for repeat notification [0-10 times] (05.11.2019) + - settings for continious notification while phone ring [on, off] (06.11.2019) + - settings for send once notification for missed call [on, off] (06.11.2019) + - on text message, or other application + - on triger phone alarm (05.11.2019) + + Call handling + - setting for ignore/reject call with watch button [on->reject call, off->ignore call] (06.11.2019) + - setting for ignore/reject call with shake device - duplicates button action [on, off] (06.11.2019) + - on watch - show small phone icon near bluetooth icon when there are missed call (06.11.2019) + + Calibrations + - time calibration + * send current date/time to watch + - set watch alarms + - altitude calibration [altitude (meters)] (04.11.2019) + * it's used in Climb activity + - status of blood pressure calibration (06.11.2019) + * it's used in blood pressure measurement + + Device settings + - lift wrist to screen on [on, off,TODO scheduled] (02.11.2019) + - change time format 12/24h (02.11.2019) + - disconnect reminder [on, off,TODO scheduled] (02.11.2019) + - find my phone [on, off, ring duration] (02.11.2019) + + Activity data + - get steps per day + - get heart reate measurements + - get sleep data + - set user goal for steps (TODO set user details) + + Changed in app device icon (02.11.2019) + Get blood pressure measurement result (work only if blood pressure is calibrated) + Pairing activity + From bf3889e51957589a086bb57113b1a1e99dbef6d7 Mon Sep 17 00:00:00 2001 From: mamutcho Date: Fri, 8 Nov 2019 19:24:50 +0200 Subject: [PATCH 019/546] work progress --- .../devices/lenovo/watchxplus/WorkProgress | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress new file mode 100644 index 000000000..284038f85 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress @@ -0,0 +1,83 @@ +NEDD TO BE DONE + Watch settings + - Set watch modes (energy saving): + - Normal -> the watch work normally + - Power-saving mode -> the app turn off the bluetooth on the watch + - Trad-watch mode -> the watch only works as an analog one + - Set watch language (currently forced to English) + - Set watch units (metric/imperial) + - Implement send User details to watch + - Age, height, gender, etc. + - Implement Do not disturb (on, off, scheduled) + - Implement themperature alarm on watch + - switch, lowTemp, highTemp + - Implement continious blood pressure measurement (on, off, scheduled) + - implement long sit reminder + + Add feature to initiate button press event on watch + - Send command to watch + - Get triger on button press + + Schedulers: + - Screen on scheduler (inApp, not supported by watch) + - Disconnect reminder scheduler (inApp, not supported by watch) + - Do not disturb (supported by watch) + - Continious blood pressure measurement (supported by watch) + + Refine send weather to watch + - Send weather icon + + Refine get activity data + - Fix get sleep data + + Measurements + - Blood pressure mesurement + - Add blood pressure calibration (view) + - Add blood pressure calibration function + - Show blood pressure measurement (view) + - Implement heart rate measurement + - Implement themperature measurement + - Implement UV index measurement + + + +WORK PROGRESS + Send notification to watch + - on incomming call + - add function to cancel notification on watch (04.11.2019) + - cancel notification on change phone state (end call, reject call etc.) (06.11.2019) + - settings for repeat notification [0-10 times] (05.11.2019) + - settings for continious notification while phone ring [on, off] (06.11.2019) + - settings for send once notification for missed call [on, off] (06.11.2019) + - on text message, or other application + - on triger phone alarm (05.11.2019) + + Call handling + - setting for ignore/reject call with watch button [on->reject call, off->ignore call] (06.11.2019) + - setting for ignore/reject call with shake device - duplicates button action [on, off] (06.11.2019) + - on watch - show small phone icon near bluetooth icon when there are missed call (06.11.2019) + + Calibrations + - time calibration + * send current date/time to watch + - set watch alarms + - altitude calibration [altitude (meters)] (04.11.2019) + * it's used in Climb activity + - status of blood pressure calibration (06.11.2019) + * it's used in blood pressure measurement + + Device settings + - lift wrist to screen on [on, off,TODO scheduled] (02.11.2019) + - change time format 12/24h (02.11.2019) + - disconnect reminder [on, off,TODO scheduled] (02.11.2019) + - find my phone [on, off, ring duration] (02.11.2019) + + Activity data + - get steps per day + - get heart reate measurements + - get sleep data + - set user goal for steps (TODO set user details) + + Changed in app device icon (02.11.2019) + Get blood pressure measurement result (work only if blood pressure is calibrated) + Pairing activity \ No newline at end of file From 3b5ce7ba531ce1a0fd4cb44c356bb6962c4ebb2f Mon Sep 17 00:00:00 2001 From: Mamut Date: Fri, 8 Nov 2019 19:27:04 +0200 Subject: [PATCH 020/546] Delete WorkProgress --- .../devices/lenovo/watchxplus/WorkProgress | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress deleted file mode 100644 index 8203a2cd9..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress +++ /dev/null @@ -1,83 +0,0 @@ -TODO - Watch settings - - Set watch modes (energy saving): - - Normal -> the watch work normally - - Power-saving mode -> the app turn off the bluetooth on the watch - - Trad-watch mode -> the watch only works as an analog one - - Set watch language (currently forced to English) - - Set watch units (metric/imperial) - - Implement send User details to watch - - Age, height, gender, etc. - - Implement Do not disturb (on, off, scheduled) - - Implement themperature alarm on watch - - switch, lowTemp, highTemp - - Implement continious blood pressure measurement (on, off, scheduled) - - Add feature to initiate button press event on watch - - Send command to watch - - Get triger on button press - - Schedulers: - - Screen on scheduler (inApp, not supported by watch) - - Disconnect reminder scheduler (inApp, not supported by watch) - - Do not disturb (supported by watch) - - Continious blood pressure measurement (supported by watch) - - Refine send weather to watch - - Send weather icon - - Refine get activity data - - Fix get sleep data - - Measurements - - Blood pressure mesurement - - Add blood pressure calibration (view) - - Add blood pressure calibration function - - Show blood pressure measurement (view) - - Implement heart rate measurement - - Implement themperature measurement - - Implement UV index measurement - - - -WORK PROGRESS - Send notification to watch - - on incomming call - - add function to cancel notification on watch (04.11.2019) - - cancel notification on change phone state (end call, reject call etc.) (06.11.2019) - - settings for repeat notification [0-10 times] (05.11.2019) - - settings for continious notification while phone ring [on, off] (06.11.2019) - - settings for send once notification for missed call [on, off] (06.11.2019) - - on text message, or other application - - on triger phone alarm (05.11.2019) - - Call handling - - setting for ignore/reject call with watch button [on->reject call, off->ignore call] (06.11.2019) - - setting for ignore/reject call with shake device - duplicates button action [on, off] (06.11.2019) - - on watch - show small phone icon near bluetooth icon when there are missed call (06.11.2019) - - Calibrations - - time calibration - * send current date/time to watch - - set watch alarms - - altitude calibration [altitude (meters)] (04.11.2019) - * it's used in Climb activity - - status of blood pressure calibration (06.11.2019) - * it's used in blood pressure measurement - - Device settings - - lift wrist to screen on [on, off,TODO scheduled] (02.11.2019) - - change time format 12/24h (02.11.2019) - - disconnect reminder [on, off,TODO scheduled] (02.11.2019) - - find my phone [on, off, ring duration] (02.11.2019) - - Activity data - - get steps per day - - get heart reate measurements - - get sleep data - - set user goal for steps (TODO set user details) - - Changed in app device icon (02.11.2019) - Get blood pressure measurement result (work only if blood pressure is calibrated) - Pairing activity - From 3a2b718f09836b2c39c6ca8f18461bf46e3df10d Mon Sep 17 00:00:00 2001 From: mamutcho Date: Sat, 9 Nov 2019 22:16:10 +0200 Subject: [PATCH 021/546] Blood Pressure calibration --- .../activities/SettingsActivity.java | 13 +++ .../watchxplus/WatchXPlusConstants.java | 6 +- .../WatchXPlusDeviceCoordinator.java | 9 +- .../watchxplus/WatchXPlusDeviceSupport.java | 106 +++++++++++++----- app/src/main/res/values-bg/strings.xml | 5 +- app/src/main/res/values/strings.xml | 5 + app/src/main/res/xml/preferences.xml | 24 ++++ 7 files changed, 128 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java index 25fff1030..b12a71711 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java @@ -58,6 +58,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActi import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus.WatchXPlusDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -90,6 +91,7 @@ public class SettingsActivity extends AbstractSettingsActivity { Prefs prefs = GBApplication.getPrefs(); Preference pref = findPreference("notifications_generic"); + pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); @@ -209,6 +211,17 @@ public class SettingsActivity extends AbstractSettingsActivity { }); + pref = findPreference("watchxplus_button_BP_calibration"); + pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newVal) { + LOG.info(" uhaaaa "); + preference.setSummary("Calibrating, please wait... (if no result after 15s. re-run)"); + GBApplication.deviceService().onSendConfiguration("BP_CAL"); + return true; + } + }); + final Preference unit = findPreference(PREF_MEASUREMENT_SYSTEM); unit.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override 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 bc25a933e..42a739a12 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 @@ -41,6 +41,8 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { 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"; + public static final String PREF_BP_CAL_LOW = "pref_wxp_bp_calibration_low"; + public static final String PREF_BP_CAL_HIGH = "pref_wxp_bp_calibration_high"; // time format constants @@ -64,6 +66,7 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] CMD_NOTIFICATION_CANCEL = new byte[]{0x03, 0x04}; public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02}; public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61}; + public static final byte[] CMD_POWER_MODE = new byte[]{0x03, -0x7F}; //bArr[8] - 0 normal, 1 poser save, 2 green public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02}; public static final byte[] CMD_DAY_STEPS_INFO = new byte[]{0x10, 0x03}; @@ -77,10 +80,11 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { 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_BP_CALIBRATION = new byte[]{0x08, 0x05, 0x0C}; 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}; - public static final byte[] RESP_HEARTRATE = new byte[]{-0x80, 0x15, 0x03}; + public static final byte[] RESP_HEARTRATE = new byte[]{(byte) 0x80, 0x15, 0x03}; public static final byte[] RESP_DATA_COUNT = new byte[]{0x08, (byte)0xF0, 0x10}; public static final byte[] RESP_DATA_DETAILS = new byte[]{0x08, (byte)0xF0, 0x11}; 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 6814968fd..dd24b6760 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 @@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus.WatchXPlusDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext; @@ -223,14 +224,6 @@ 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 */ 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 fe5d55256..590fa09c3 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,12 +25,18 @@ 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 android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; import androidx.annotation.IntRange; +import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.google.android.material.snackbar.Snackbar; + import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,6 +56,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; @@ -64,7 +71,6 @@ 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; @@ -77,7 +83,6 @@ 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; @@ -87,12 +92,13 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateA 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.GB; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { - + protected static Prefs prefs = GBApplication.getPrefs(); private boolean needsAuth; private int sequenceNumber = 0; private boolean isCalibrationActive = false; @@ -568,6 +574,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } + + @Override public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { @@ -655,7 +663,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onFindDevice(boolean start) { - } @Override @@ -665,7 +672,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onScreenshotReq() { - + sendBloodPressureCalibration(); } @Override @@ -707,11 +714,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { getDisconnectReminderStatus(builder); break; case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT: - setTimeMode(builder, sharedPreferences); + setTimeFormat(builder, sharedPreferences); break; - case WatchXPlusConstants.PREF_ALTITUDE: - LOG.info(" ALTITUDE: " + config); - + case "BP_CAL": + sendBloodPressureCalibration(); break; } builder.queue(getQueue()); @@ -739,18 +745,64 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } + CoordinatorLayout coordinatorLayout; + +// check status of blood pressure calibration + private WatchXPlusDeviceSupport sendBloodPressureCalibration() { + try { + int mLowP = prefs.getInt(WatchXPlusConstants.PREF_BP_CAL_LOW, 80); + int mHighP = prefs.getInt(WatchXPlusConstants.PREF_BP_CAL_HIGH, 130); + LOG.warn("Calibrating BP ... LowP=" + mLowP + " HighP="+mHighP); + GB.toast("Calibrating BP...", Toast.LENGTH_LONG, GB.INFO); + + TransactionBuilder builder = performInitialized("bpCalibrate"); + + byte[] command = WatchXPlusConstants.CMD_BP_CALIBRATION; + byte mStart = 0x01; + + byte[] bArr = new byte[5]; + bArr[0] = (byte) mStart; // byte[08] + bArr[1] = (byte) (mHighP >> 8); // byte[09] + bArr[2] = (byte) mHighP; // byte[10] + bArr[3] = (byte) (mLowP >> 8); // byte[11] + bArr[4] = (byte) mLowP; // byte[12] + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.TASK, + bArr)); + builder.queue(getQueue()); } catch (IOException e) { + LOG.warn("Unable to send BP Calibration", e); + } + return this; + } + private void handleBloodPressureCalibrationStatus(byte[] value) { - if (Conversion.calculateHigh(value[8]) != 0) { + if (Conversion.fromByteArr16(value[8]) != 0) { WatchXPlusDeviceCoordinator.isBPCalibrated = false; } else { WatchXPlusDeviceCoordinator.isBPCalibrated = true; } } + + + private void handleBloodPressureCalibrationResult(byte[] value) { + if (Conversion.fromByteArr16(value[8]) != 0x00) { + WatchXPlusDeviceCoordinator.isBPCalibrated = false; + GB.toast("Calibrating BP fail", Toast.LENGTH_LONG, GB.ERROR); + } else { + WatchXPlusDeviceCoordinator.isBPCalibrated = true; + int high = Conversion.fromByteArr16(value[9], value[10]); + int low = Conversion.fromByteArr16(value[11], value[12]); + GB.toast("OK. Measured Low:"+low+" high:"+high, Toast.LENGTH_LONG, GB.INFO); + } + } // end check status of blood pressure calibration private void requestBloodPressureMeasurement() { if (!WatchXPlusDeviceCoordinator.isBPCalibrated) { LOG.warn("BP is NOT calibrated"); + GB.toast("BP is not calibrated", Toast.LENGTH_LONG, GB.WARN); return; } try { @@ -836,6 +888,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { handleTime(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_IS_BP_CALIBRATED, 5)) { handleBloodPressureCalibrationStatus(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BP_CALIBRATION, 5)) { + handleBloodPressureCalibrationResult(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_BUTTON_INDICATOR, 5)) { this.onReverseFindDevice(true); // It looks like WatchXPlus doesn't send this action @@ -874,7 +928,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return true; } else if (WatchXPlusConstants.UUID_CHARACTERISTIC_DATABASE_READ.equals(characteristicUUID)) { - + LOG.info(" Value change for characteristic DATABASE: " + characteristicUUID + " value " + value); handleContentDataChunk(value); return true; } else { @@ -1123,12 +1177,13 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { if (value.length < 11) { LOG.info(" BP Measure started. Waiting for result"); + GB.toast("BP Measure started. Waiting for result...", Toast.LENGTH_LONG, GB.INFO); } else { LOG.info(" Received BP live data"); int high = Conversion.fromByteArr16(value[8], value[9]); int low = Conversion.fromByteArr16(value[10], value[11]); int timestamp = Conversion.fromByteArr16(value[12], value[13], value[14], value[15]); - + GB.toast("Calculated BP data: low: " + low + ", high: " + high, Toast.LENGTH_LONG, GB.INFO); LOG.info(" Calculated BP data: timestamp: " + timestamp + ", high: " + high + ", low: " + low); } } @@ -1341,7 +1396,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()); this.setHeadsUpScreen(transaction, sharedPreferences); // lift wirst to screen on this.setDisconnectReminder(transaction, sharedPreferences); // disconnect reminder - this.setTimeMode(transaction, sharedPreferences); // set time mode 12/24h + this.setTimeFormat(transaction, sharedPreferences); // set time mode 12/24h this.setAltitude(transaction); // set altitude calibration } @@ -1437,7 +1492,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } // set time format - private WatchXPlusDeviceSupport setTimeMode(TransactionBuilder transactionBuilder, byte timeMode) { + private WatchXPlusDeviceSupport setTimeFormat(TransactionBuilder transactionBuilder, byte timeMode) { byte[] bArr = new byte[2]; bArr[0] = 0x01; //byte[08] language - force to English language bArr[1] = timeMode; //byte[09] time @@ -1445,7 +1500,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { buildCommand(WatchXPlusConstants.CMD_TIME_LANGUAGE, WatchXPlusConstants.WRITE_VALUE, bArr)); - //LOG.info(" setTimeMode: " + bArr); return this; } @@ -1470,8 +1524,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } - private WatchXPlusDeviceSupport setTimeMode(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { - return this.setTimeMode(transactionBuilder, + private WatchXPlusDeviceSupport setTimeFormat(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { + return this.setTimeFormat(transactionBuilder, WatchXPlusDeviceCoordinator.getTimeMode(sharedPreferences)); } @@ -1501,6 +1555,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } + + private static class Conversion { static byte toBcd8(@IntRange(from = 0, to = 99) int value) { int high = (value / 10) << 4; @@ -1518,7 +1574,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return new byte[]{(byte) (value >> 8), (byte) value}; } - static int fromByteArr16(byte... value) { + static int fromByteArr16(byte... value) { // equals calculateHigh int intValue = 0; for (int i2 = 0; i2 < value.length; i2++) { intValue += (value[i2] & 255) << (((value.length - 1) - i2) * 8); @@ -1542,15 +1598,5 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } 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 3b7f192df..5d83bcc25 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -289,8 +289,11 @@ Калибриране на височина Повтори известия за звънене Възможни стойности min=0, max=10 - Известие докато звъни + Известявай докато телефона звъни Изкл. - игнорира, Вкл. - отказ + Калибриране на кръвно налягане + Кръвно налягане DIASTOLIC (долна) + Кръвно налягане SYSTOLIC (горна) Бутона игнорира/отказва повикване Дублира действието на бутона Разклащането игнорира/отказва повикване diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fe73c3cd3..98e0eb93d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -199,6 +199,11 @@ Button ignore/reject call Duplicates watch button action Shake wrist ignore/reject call + Blood Pressure calibration + Blood Pressure DIASTOLIC (low) + Blood Pressure SYSTOLIC (high) + Begin calibration + Press here to begin calibration WatchXPlus calibration Makibes HR3 settings diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index a44d6e6e5..1b998ef85 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -620,6 +620,30 @@ android:key="watchxplus_altitude" android:title="@string/pref_wxp_title_altitude"/> + + + + + + + From 24439008abbcc29dea5809dfc1e31b0414f2d636 Mon Sep 17 00:00:00 2001 From: mamutcho Date: Sun, 10 Nov 2019 23:15:13 +0200 Subject: [PATCH 022/546] Add watch power mode and DND --- .../activities/SettingsActivity.java | 16 +- .../watchxplus/WatchXPlusConstants.java | 11 +- .../WatchXPlusDeviceCoordinator.java | 40 ++++- .../devices/lenovo/watchxplus/WorkProgress | 55 +++--- .../watchxplus/WatchXPlusDeviceSupport.java | 170 ++++++++++++++---- app/src/main/res/values-bg/strings.xml | 118 +++++++++++- app/src/main/res/values/arrays.xml | 11 ++ app/src/main/res/values/strings.xml | 5 + app/src/main/res/xml/preferences.xml | 35 +++- 9 files changed, 384 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java index b12a71711..9ad6d7dc0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java @@ -91,7 +91,6 @@ public class SettingsActivity extends AbstractSettingsActivity { Prefs prefs = GBApplication.getPrefs(); Preference pref = findPreference("notifications_generic"); - pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { Intent enableIntent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); @@ -211,13 +210,22 @@ public class SettingsActivity extends AbstractSettingsActivity { }); - pref = findPreference("watchxplus_button_BP_calibration"); + pref = findPreference("wxp_button_BP_calibration"); + pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newVal) { + preference.setSummary("Calibrating, please wait... (if no result after 15s. re-run)"); + GBApplication.deviceService().onSendConfiguration("BP_CAL"); + return true; + } + }); + + pref = findPreference("wxp_power_mode"); pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newVal) { LOG.info(" uhaaaa "); - preference.setSummary("Calibrating, please wait... (if no result after 15s. re-run)"); - GBApplication.deviceService().onSendConfiguration("BP_CAL"); + GBApplication.deviceService().onSendConfiguration("WXP_POWER_MODE"); return true; } }); 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 42a739a12..424eb2ab3 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 @@ -43,7 +43,9 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final String PREF_SHAKE_REJECT = "watchxplus_shake_reject"; public static final String PREF_BP_CAL_LOW = "pref_wxp_bp_calibration_low"; public static final String PREF_BP_CAL_HIGH = "pref_wxp_bp_calibration_high"; - + public static final String PREF_DO_NOT_DISTURB = "do_not_disturb_no_auto"; + public static final String PREF_DO_NOT_DISTURB_START = "do_not_disturb_no_auto_start"; + public static final String PREF_DO_NOT_DISTURB_END = "do_not_disturb_no_auto_end"; // time format constants public static final byte ARG_SET_TIMEMODE_24H = 0x00; @@ -66,7 +68,10 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] CMD_NOTIFICATION_CANCEL = new byte[]{0x03, 0x04}; public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02}; public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61}; - public static final byte[] CMD_POWER_MODE = new byte[]{0x03, -0x7F}; //bArr[8] - 0 normal, 1 poser save, 2 green + public static final byte[] CMD_POWER_MODE = new byte[]{0x03, -0x7F}; + public static final byte[] CMD_SET_QUITE_HOURS_TIME = new byte[]{0x03, 0x62}; + public static final byte[] CMD_SET_QUITE_HOURS_SWITCH = new byte[]{0x03, 0x61}; + public static final byte[] CMD_SET_PERSONAL_INFO = new byte[]{0x01, 0x0E}; public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02}; public static final byte[] CMD_DAY_STEPS_INFO = new byte[]{0x10, 0x03}; @@ -81,6 +86,8 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { 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_BP_CALIBRATION = new byte[]{0x08, 0x05, 0x0C}; + public static final byte[] RESP_SET_PERSONAL_INFO = new byte[]{0x08, 0x01, 0x0E}; + public static final byte[] RESP_GOAL_AIM_STATUS = new byte[]{0x08, 0x10, 0x02}; 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 dd24b6760..6f64f5807 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 @@ -12,6 +12,12 @@ import android.os.ParcelUuid; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Collection; import java.util.Collections; @@ -34,8 +40,9 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext; -public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { +public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { + private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusDeviceSupport.class); public static final int FindPhone_ON = -1; public static final int FindPhone_OFF = 0; public static boolean isBPCalibrated = false; @@ -163,7 +170,8 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { R.xml.devicesettings_liftwrist_display, R.xml.devicesettings_disconnectnotification, R.xml.devicesettings_find_phone, - R.xml.devicesettings_timeformat + R.xml.devicesettings_timeformat, + R.xml.devicesettings_donotdisturb_no_auto }; } @@ -224,6 +232,34 @@ Prefs from device settings on main page } } + /** + * @param startOut out Only hour/minute are used. + * @param endOut out Only hour/minute are used. + * @return True if quite hours are enabled. + */ + public static boolean getQuiteHours(SharedPreferences sharedPrefs, Calendar startOut, Calendar endOut) { + String doNotDisturb = sharedPrefs.getString(WatchXPlusConstants.PREF_DO_NOT_DISTURB, getContext().getString(R.string.p_off)); + + if (doNotDisturb.equals(getContext().getString(R.string.p_off))) { + LOG.info(" DND is disabled "); + return false; + } else { + String start = sharedPrefs.getString(WatchXPlusConstants.PREF_DO_NOT_DISTURB_START, "00:00"); + String end = sharedPrefs.getString(WatchXPlusConstants.PREF_DO_NOT_DISTURB_END, "00:00"); + + DateFormat df = new SimpleDateFormat("HH:mm"); + + try { + startOut.setTime(df.parse(start)); + endOut.setTime(df.parse(end)); + + return true; + } catch (Exception e) { + return false; + } + } + } + /* Values from device specific settings page */ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress index 284038f85..e34cb75e0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress @@ -1,27 +1,19 @@ NEDD TO BE DONE Watch settings - - Set watch modes (energy saving): - - Normal -> the watch work normally - - Power-saving mode -> the app turn off the bluetooth on the watch - - Trad-watch mode -> the watch only works as an analog one - Set watch language (currently forced to English) - Set watch units (metric/imperial) - - Implement send User details to watch - - Age, height, gender, etc. - - Implement Do not disturb (on, off, scheduled) - - Implement themperature alarm on watch + - Implement temperature alarm on watch - switch, lowTemp, highTemp - Implement continious blood pressure measurement (on, off, scheduled) - - implement long sit reminder + - Implement long sit reminder Add feature to initiate button press event on watch - Send command to watch - - Get triger on button press + - Get trigger on button press Schedulers: - Screen on scheduler (inApp, not supported by watch) - Disconnect reminder scheduler (inApp, not supported by watch) - - Do not disturb (supported by watch) - Continious blood pressure measurement (supported by watch) Refine send weather to watch @@ -32,51 +24,56 @@ NEDD TO BE DONE Measurements - Blood pressure mesurement - - Add blood pressure calibration (view) - - Add blood pressure calibration function - Show blood pressure measurement (view) - Implement heart rate measurement - - Implement themperature measurement + - Implement temperature measurement - Implement UV index measurement WORK PROGRESS Send notification to watch - - on incomming call + - On incoming call - add function to cancel notification on watch (04.11.2019) - cancel notification on change phone state (end call, reject call etc.) (06.11.2019) - settings for repeat notification [0-10 times] (05.11.2019) - settings for continious notification while phone ring [on, off] (06.11.2019) - settings for send once notification for missed call [on, off] (06.11.2019) - - on text message, or other application - - on triger phone alarm (05.11.2019) + - On text message, or other application + - On triger phone alarm (05.11.2019) Call handling - - setting for ignore/reject call with watch button [on->reject call, off->ignore call] (06.11.2019) - - setting for ignore/reject call with shake device - duplicates button action [on, off] (06.11.2019) - - on watch - show small phone icon near bluetooth icon when there are missed call (06.11.2019) + - Setting for ignore/reject call with watch button [on->reject call, off->ignore call] (06.11.2019) + - Setting for ignore/reject call with shake device - duplicates button action [on, off] (06.11.2019) + - On watch - show small phone icon near bluetooth icon when there are missed call (06.11.2019) Calibrations - - time calibration + - Time calibration * send current date/time to watch - - set watch alarms - - altitude calibration [altitude (meters)] (04.11.2019) + - Set watch alarms + - Altitude calibration [altitude (meters)] (04.11.2019) * it's used in Climb activity - - status of blood pressure calibration (06.11.2019) + - Status of blood pressure calibration (06.11.2019) * it's used in blood pressure measurement + - Blood pressure calibration (09.11.2019) (TODO fix button initiating calibration) Device settings - - lift wrist to screen on [on, off,TODO scheduled] (02.11.2019) - - change time format 12/24h (02.11.2019) - - disconnect reminder [on, off,TODO scheduled] (02.11.2019) - - find my phone [on, off, ring duration] (02.11.2019) + - Lift wrist to screen on [on, off,TODO scheduled] (02.11.2019) + - Change time format 12/24h (02.11.2019) + - Disconnect reminder [on, off,TODO scheduled] (02.11.2019) + - Find my phone [on, off, ring duration] (02.11.2019) + - Set watch modes (energy saving) (10.11.2019) (Need testing) + - Normal -> the watch work normally + - Power-saving mode -> the app turn off the bluetooth on the watch + - Trad-watch mode -> the watch only works as an analog one + - Do not disturb [on, off, scheduled] (10.11.2019) (need reconnect to apply) + - Send User details to watch [height, weight, age, gender] (10.11.2019) (need more testing) Activity data - get steps per day - get heart reate measurements - get sleep data - - set user goal for steps (TODO set user details) + - set user goal for steps Changed in app device icon (02.11.2019) Get blood pressure measurement result (work only if blood pressure is calibrated) 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 590fa09c3..1fd5186a2 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 @@ -26,18 +26,12 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.Uri; import android.os.Handler; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; import android.widget.Toast; import androidx.annotation.IntRange; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import com.google.android.material.snackbar.Snackbar; - -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -276,15 +270,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } - private WatchXPlusDeviceSupport enableDoNotDisturb(TransactionBuilder builder, boolean active) { - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_DO_NOT_DISTURB_SETTINGS, - WatchXPlusConstants.WRITE_VALUE, - new byte[]{(byte) (active ? 0x01 : 0x00)})); - - return this; - } - private void enableCalibration(boolean enable) { try { TransactionBuilder builder = performInitialized("enableCalibration"); @@ -399,21 +384,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } - private WatchXPlusDeviceSupport setFitnessGoal(TransactionBuilder builder) { - int fitnessGoal = new ActivityUser().getStepsGoal(); - builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_FITNESS_GOAL_SETTINGS, - WatchXPlusConstants.WRITE_VALUE, - Conversion.toByteArr16(fitnessGoal))); - - return this; - } - public WatchXPlusDeviceSupport initialize(TransactionBuilder builder) { getFirmwareVersion(builder) .getBatteryState(builder) .enableNotificationChannels(builder) - .enableDoNotDisturb(builder, false) .setFitnessGoal(builder) .getBloodPressureCalibrationStatus(builder) .syncPreferences(builder); @@ -574,8 +548,43 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } + private WatchXPlusDeviceSupport setFitnessGoal(TransactionBuilder builder) { + int fitnessGoal = new ActivityUser().getStepsGoal(); + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_FITNESS_GOAL_SETTINGS, + WatchXPlusConstants.WRITE_VALUE, + Conversion.toByteArr16(fitnessGoal))); + return this; + } +// set personal info + private WatchXPlusDeviceSupport setPersonalInformation(TransactionBuilder builder, int height, int weight, int age, int gender) { + LOG.warn(" Setting Personal Information... height:"+height+" weight:"+weight+" age:"+age+" gender:"+gender); + byte[] command = WatchXPlusConstants.CMD_SET_PERSONAL_INFO; + + byte[] bArr = new byte[4]; + bArr[0] = (byte) height; // byte[08] + bArr[1] = (byte) weight; // byte[09] + bArr[2] = (byte) age; // byte[10] + bArr[3] = (byte) gender; // byte[11] + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, + WatchXPlusConstants.WRITE_VALUE, + bArr)); + return this; + } + +// handle get/set personal info +// for test purposes only + private void handlePersonalInfo(byte[] value) { + int height = Conversion.fromByteArr16(value[8]); + int weight = Conversion.fromByteArr16(value[9]); + int age = Conversion.fromByteArr16(value[10]); + int gender = Conversion.fromByteArr16(value[11]); + LOG.info(" Personal info - height:" + height + ", weight:" + weight + ", age:" + age + ", gender:" + gender); + } @Override public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { @@ -648,7 +657,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onReset(int flags) { - + // testNewCommands(); } @Override @@ -701,6 +710,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()); try { builder = performInitialized("sendConfig: " + config); + LOG.info(" config changed:" + config); switch (config) { case ActivityUser.PREF_USER_STEPS_GOAL: setFitnessGoal(builder); @@ -716,9 +726,18 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT: setTimeFormat(builder, sharedPreferences); break; + case WatchXPlusConstants.PREF_DO_NOT_DISTURB: + case WatchXPlusConstants.PREF_DO_NOT_DISTURB_START: + case WatchXPlusConstants.PREF_DO_NOT_DISTURB_END: + LOG.info(" bravo "); + setQuiteHours(builder, sharedPreferences); + break; case "BP_CAL": sendBloodPressureCalibration(); break; + case "WXP_POWER_MODE": + setPowerMode(config); + break; } builder.queue(getQueue()); } catch (IOException e) { @@ -736,6 +755,71 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { requestBloodPressureMeasurement(); } +// set do not disturb time + private WatchXPlusDeviceSupport setQuiteHours(TransactionBuilder tbuilder, boolean enable, int hourStart, int minuteStart, int hourEnd, int minuteEnd) { + LOG.warn(" Setting DND time... Hs:"+hourStart+" Ms:"+minuteStart+" He:"+hourEnd+" Me:"+minuteEnd); + byte[] command = WatchXPlusConstants.CMD_SET_QUITE_HOURS_TIME; + + byte[] bArr = new byte[4]; + bArr[0] = (byte) hourStart; // byte[08] + bArr[1] = (byte) minuteStart; // byte[09] + bArr[2] = (byte) hourEnd; // byte[10] + bArr[3] = (byte) minuteEnd; // byte[11] + + tbuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_SET_QUITE_HOURS_TIME, + WatchXPlusConstants.WRITE_VALUE, + bArr)); + setQuiteHoursSwitch(tbuilder, enable); + return this; + } + + // set do not disturb switch + private WatchXPlusDeviceSupport setQuiteHoursSwitch(TransactionBuilder tbuilder, boolean enable) { + LOG.warn("Setting DND switch to" + enable); + tbuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_SET_QUITE_HOURS_SWITCH, + WatchXPlusConstants.WRITE_VALUE, + new byte[]{(byte) (enable ? 0x01 : 0x00)})); + return this; + } + + private WatchXPlusDeviceSupport setQuiteHours(TransactionBuilder builder, SharedPreferences sharedPreferences) { + Calendar start = new GregorianCalendar(); + Calendar end = new GregorianCalendar(); + boolean enable = WatchXPlusDeviceCoordinator.getQuiteHours(sharedPreferences, start, end); + if (enable) { + return this.setQuiteHours(builder, enable, + start.get(Calendar.HOUR_OF_DAY), start.get(Calendar.MINUTE), + end.get(Calendar.HOUR_OF_DAY), end.get(Calendar.MINUTE)); + } else { + LOG.info(" Quiet hours are disabled"); + return this.setQuiteHoursSwitch(builder, enable); + } + } + +// set watch power mode + private WatchXPlusDeviceSupport setPowerMode(String config) { + int settingRead = prefs.getInt("wxp_power_mode", 0); + byte[] bArr = new byte[1]; + if (settingRead == 0) bArr[0] = 0x00; + if (settingRead == 1) bArr[0] = 0x01; + if (settingRead == 2) bArr[0] = 0x02; + LOG.info(" setting: " + config + " mode: " + bArr[0]); + + try { + TransactionBuilder builder = performInitialized("setPowerMode"); + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_POWER_MODE, + WatchXPlusConstants.TASK, + bArr)); + builder.queue(getQueue()); + } catch (IOException e) { + LOG.warn("Unable to set power mode", e); + } + return this; + } + // check status of blood pressure calibration private WatchXPlusDeviceSupport getBloodPressureCalibrationStatus(TransactionBuilder builder) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), @@ -771,7 +855,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { buildCommand(command, WatchXPlusConstants.TASK, bArr)); - builder.queue(getQueue()); } catch (IOException e) { + builder.queue(getQueue()); + } catch (IOException e) { LOG.warn("Unable to send BP Calibration", e); } return this; @@ -820,27 +905,27 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } - /* + // not working!!! - private void requestHeartRateMeasurement() { + private void testNewCommands() { try { - TransactionBuilder builder = performInitialized("hrMeasure"); + TransactionBuilder builder = performInitialized("test"); - byte[] command = new byte[]{0x05, 0x0B}; + int first = prefs.getInt("wxp_newcmd_first", 0); + int second = prefs.getInt("wxp_newcmd_second", 0); + byte[] command = new byte[]{(byte) first, (byte) second}; + LOG.info("testing new command " + command); 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) { @@ -878,12 +963,16 @@ 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_SET_PERSONAL_INFO, 5)) { + handlePersonalInfo(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)) { handleBatteryState(value); + } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_GOAL_AIM_STATUS, 5)) { + handleSportAimStatus(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_TIME_SETTINGS, 5)) { handleTime(value); } else if (ArrayUtils.equals(value, WatchXPlusConstants.RESP_IS_BP_CALIBRATED, 5)) { @@ -1214,6 +1303,11 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return result; } + private void handleSportAimStatus(byte[] value) { + int stepsAim = Conversion.fromByteArr16(value[8], value[9]); + LOG.debug(" Received goal stepsAim: " + stepsAim); + } + private void handleStepsInfo(byte[] value) { int steps = Conversion.fromByteArr16(value[8], value[9]); LOG.debug(" Received steps count: " + steps); @@ -1395,9 +1489,13 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { private void syncPreferences(TransactionBuilder transaction) { SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(this.getDevice().getAddress()); this.setHeadsUpScreen(transaction, sharedPreferences); // lift wirst to screen on + this.setQuiteHours(transaction, sharedPreferences); // DND this.setDisconnectReminder(transaction, sharedPreferences); // disconnect reminder this.setTimeFormat(transaction, sharedPreferences); // set time mode 12/24h this.setAltitude(transaction); // set altitude calibration + ActivityUser activityUser = new ActivityUser(); + this.setPersonalInformation(transaction, activityUser.getHeightCm(), activityUser.getWeightKg(), + activityUser.getAge(),activityUser.getGender()); } private Handler mFindPhoneHandler = new Handler(); diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 5d83bcc25..920f0e19d 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -284,7 +284,7 @@ Watch 9 свързване Watch 9 сверяване WatchX Plus сверяване - Edinici + Единици Формат на часа Калибриране на височина Повтори известия за звънене @@ -466,4 +466,120 @@ Стартирайте активност Специфични настройки за устройството Средно: %1$s + Отказ + Изтрий + Дълбок сън + Лек сън + Не е носен + Активност + Отговори + Управление на базата + Изчисти БД + Импорт БД + Изтрий стара БД + Експорт на БД + Експортиране на БД... + Стартирай авто експортиране + Локацията за експорт на БД е: + АвтоЕкспорт + ИзпразниБД + Внимание! Ако натиснете този бутон БД ще се изтрие. + Експорт и Импорт + Време за сън в часове + Активности + Активност + Колоездене + Дълбок сън + Лек сън + Упражнение + Не е измерено + Не е носено + Бягане + Плуване + Неизвестна активност + Ходене + Не е свързан, алармата не е настроена. + Аларма за %1$02d:%2$02d + Автоматично + Удостоверяване + Изисква удостоверяване + Сърдечна честота + Избери локация за експорт + Час + Замени + Firmware версия: %1$s + Hardware версия: %1$s + Не е свързан. + Неизвестно устройство + Опит за свързване с %1$s + Не се свързвай + Включи bluetooth за намиране на устройства. + Свързване с %1$s? + Опит за свързване с: %1$s + Свържи + Не филтрирай + Намери ме! + Шрифт + Невалидни данни + Сърдечен ритъм + Продължителност + Аларма + Активност + Компас + Музика + Настройки + Известия + Прогноза за време + Таймер + Изкл. + Разписание + Край + Начало + Не безпокой + Вие спахте от %1$s до %2$s + Режим на часовника + Само часовник + Икономичен + Нормален + Стъпки: %1$02d + Сън: %1$s + 5 минути + 20 минути + 1 час + 10 минути + Крачки за месец + Сън за месец + Внимание! + Изчаква свързване + Всички аларми са изключени + Метрична + Инчова + Въведете поне една дума + Филтъра за известия е запазен + Вибрация + Филтър за известия + Управление на БД + Избери всички + Сподели + Запази настройките + Натисни тук за старт на калибрирането + Започни калибриране + Горна граница + Долна граница + От ляво на дясно + Аларми + WatchXPlus настройки + метрични + инчови + крачки + разписание + изкл. + вкл. + изкл. + прогноза + настройки + известия + Изкл. + Вкл. + Няма данни \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 6a38a6e9b..c766f389d 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -69,6 +69,17 @@ never + + @string/wxp_mode_normal + @string/wxp_mode_saving + @string/wxp_mode_watch + + + 0 + 1 + 2 + + @string/male @string/female diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98e0eb93d..2dd8fb0ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -205,6 +205,11 @@ Begin calibration Press here to begin calibration WatchXPlus calibration + WatchXPlus settings + Watch mode + Normal + Power saving + Only watch Makibes HR3 settings diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 1b998ef85..c84357aee 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -611,8 +611,20 @@ android:summary="@string/pref_wxp_title_shake_reject_summary" android:title="@string/prefs_wxp_shake_reject" /> + + + + - - + From be47541a5eba9992c5345f1dabcdcfe6902efddc Mon Sep 17 00:00:00 2001 From: mamutcho Date: Sun, 17 Nov 2019 14:33:26 +0200 Subject: [PATCH 023/546] Add new features - Inactivity reminder - Missed call reminder - Watch language --- .../activities/SettingsActivity.java | 33 +- .../watchxplus/WatchXPlusConstants.java | 11 +- .../WatchXPlusDeviceCoordinator.java | 42 +- .../devices/lenovo/watchxplus/WorkProgress | 23 +- .../watchxplus/WatchXPlusDeviceSupport.java | 361 ++++++++++++++---- app/src/main/res/values-bg/strings.xml | 49 ++- app/src/main/res/values/arrays.xml | 16 + app/src/main/res/values/strings.xml | 19 +- app/src/main/res/xml/preferences.xml | 47 ++- 9 files changed, 475 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java index 9ad6d7dc0..69a413916 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java @@ -54,6 +54,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; +import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusConstants; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity; import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity; @@ -210,26 +211,52 @@ public class SettingsActivity extends AbstractSettingsActivity { }); - pref = findPreference("wxp_button_BP_calibration"); + pref = findPreference("wxp_button_BP_calibration_list"); pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newVal) { - preference.setSummary("Calibrating, please wait... (if no result after 15s. re-run)"); + //preference.setSummary("Calibrating, please wait... (if no result after 15s. re-run)"); GBApplication.deviceService().onSendConfiguration("BP_CAL"); return true; } }); + pref = findPreference("watchxplus_longsit_period"); + pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newVal) { + GBApplication.deviceService().onSendConfiguration("LONG_SIT"); + return true; + } + }); + + pref = findPreference("watchxplus_longsit_switch"); + pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newVal) { + GBApplication.deviceService().onSendConfiguration("LONG_SIT"); + return true; + } + }); + pref = findPreference("wxp_power_mode"); pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newVal) { - LOG.info(" uhaaaa "); GBApplication.deviceService().onSendConfiguration("WXP_POWER_MODE"); return true; } }); + pref = findPreference(WatchXPlusConstants.PREF_WXP_LANGUAGE); + pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newVal) { + GBApplication.deviceService().onSendConfiguration("WXP_LANGUAGE"); + return true; + } + }); + final Preference unit = findPreference(PREF_MEASUREMENT_SYSTEM); unit.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override 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 424eb2ab3..f73f68a6c 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 @@ -38,14 +38,19 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { 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_MISSED_CALL_REPEAT = "watchxplus_repeat_missedcall"; 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"; public static final String PREF_BP_CAL_LOW = "pref_wxp_bp_calibration_low"; public static final String PREF_BP_CAL_HIGH = "pref_wxp_bp_calibration_high"; + public static final String PREF_BP_CAL_SWITCH = "wxp_button_BP_calibration_list"; public static final String PREF_DO_NOT_DISTURB = "do_not_disturb_no_auto"; public static final String PREF_DO_NOT_DISTURB_START = "do_not_disturb_no_auto_start"; public static final String PREF_DO_NOT_DISTURB_END = "do_not_disturb_no_auto_end"; + public static final String PREF_LONGSIT_SWITCH = "watchxplus_longsit_switch"; + public static final String PREF_LONGSIT_PERIOD = "watchxplus_longsit_period"; + public static final String PREF_WXP_LANGUAGE = "wxp_language_pref"; // time format constants public static final byte ARG_SET_TIMEMODE_24H = 0x00; @@ -72,6 +77,8 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] CMD_SET_QUITE_HOURS_TIME = new byte[]{0x03, 0x62}; public static final byte[] CMD_SET_QUITE_HOURS_SWITCH = new byte[]{0x03, 0x61}; public static final byte[] CMD_SET_PERSONAL_INFO = new byte[]{0x01, 0x0E}; + public static final byte[] CMD_INACTIVITY_REMINDER_SWITCH = new byte[]{0x03, 0x51}; + public static final byte[] CMD_INACTIVITY_REMINDER_SET = new byte[]{0x03, 0x52}; public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02}; public static final byte[] CMD_DAY_STEPS_INFO = new byte[]{0x10, 0x03}; @@ -87,7 +94,9 @@ public final class WatchXPlusConstants extends LenovoWatchConstants { public static final byte[] RESP_BUTTON_WHILE_RING = new byte[]{0x04, 0x03, 0x03}; public static final byte[] RESP_BP_CALIBRATION = new byte[]{0x08, 0x05, 0x0C}; public static final byte[] RESP_SET_PERSONAL_INFO = new byte[]{0x08, 0x01, 0x0E}; - public static final byte[] RESP_GOAL_AIM_STATUS = new byte[]{0x08, 0x10, 0x02}; + public static final byte[] RESP_GOAL_AIM_STATUS = new byte[]{0x08, 0x10, 0x02}; + public static final byte[] RESP_INACTIVITY_REMINDER_SWITCH = new byte[]{0x08, 0x03, 0x51}; + public static final byte[] RESP_INACTIVITY_REMINDER_SET = new byte[]{0x08, 0x03, 0x52}; 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 6f64f5807..977ba302d 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 @@ -178,7 +178,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { /* Prefs from device settings on main page */ -// return saved time format +// return time format pref 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))) { @@ -188,6 +188,12 @@ Prefs from device settings on main page } } +// return watch language pref + public static byte getLanguage(SharedPreferences sharedPrefs) { + int settingRead = prefs.getInt(WatchXPlusConstants.PREF_WXP_LANGUAGE, 1); + return (byte) settingRead; + } + // 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)); @@ -260,6 +266,34 @@ Prefs from device settings on main page } } + /** + * @param startOut out Only hour/minute are used. + * @param endOut out Only hour/minute are used. + * @return True if quite hours are enabled. + */ + public static boolean getLongSitHours(SharedPreferences sharedPrefs, Calendar startOut, Calendar endOut) { + boolean enabled = prefs.getBoolean(WatchXPlusConstants.PREF_LONGSIT_SWITCH, false); + + if (!enabled) { + LOG.info(" DND is disabled "); + return false; + } else { + String start = sharedPrefs.getString(WatchXPlusConstants.PREF_DO_NOT_DISTURB_START, "00:00"); + String end = sharedPrefs.getString(WatchXPlusConstants.PREF_DO_NOT_DISTURB_END, "00:00"); + + DateFormat df = new SimpleDateFormat("HH:mm"); + + try { + startOut.setTime(df.parse(start)); + endOut.setTime(df.parse(end)); + + return true; + } catch (Exception e) { + return false; + } + } + } + /* Values from device specific settings page */ @@ -283,6 +317,12 @@ Values from device specific settings page return (boolean) prefs.getBoolean(WatchXPlusConstants.PREF_MISSED_CALL, false); } +//read missed call notification + public static int getMissedCallRepeat(String address) { + return (int) prefs.getInt(WatchXPlusConstants.PREF_MISSED_CALL_REPEAT, 0); + } + + //read button reject call settings public static boolean getButtonReject(String address) { return (boolean) prefs.getBoolean(WatchXPlusConstants.PREF_BUTTON_REJECT, false); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress index e34cb75e0..f44d63653 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress @@ -1,11 +1,9 @@ -NEDD TO BE DONE +NEED TO BE DONE Watch settings - - Set watch language (currently forced to English) - Set watch units (metric/imperial) - Implement temperature alarm on watch - switch, lowTemp, highTemp - - Implement continious blood pressure measurement (on, off, scheduled) - - Implement long sit reminder + - Implement continuous blood pressure measurement (on, off, scheduled) Add feature to initiate button press event on watch - Send command to watch @@ -14,7 +12,7 @@ NEDD TO BE DONE Schedulers: - Screen on scheduler (inApp, not supported by watch) - Disconnect reminder scheduler (inApp, not supported by watch) - - Continious blood pressure measurement (supported by watch) + - Continuous blood pressure measurement (supported by watch, there are command for that, but not tested) Refine send weather to watch - Send weather icon @@ -23,11 +21,11 @@ NEDD TO BE DONE - Fix get sleep data Measurements - - Blood pressure mesurement + - Blood pressure measurement - Show blood pressure measurement (view) - - Implement heart rate measurement - - Implement temperature measurement - - Implement UV index measurement + - Implement heart rate measurement //tried to implement with no luck + - Implement temperature measurement //tried to implement with no luck + - Implement UV index measurement //tried to implement with no luck @@ -39,6 +37,7 @@ WORK PROGRESS - settings for repeat notification [0-10 times] (05.11.2019) - settings for continious notification while phone ring [on, off] (06.11.2019) - settings for send once notification for missed call [on, off] (06.11.2019) + * send missed call notification every minute for X times (17.11.2019) - On text message, or other application - On triger phone alarm (05.11.2019) @@ -55,7 +54,7 @@ WORK PROGRESS * it's used in Climb activity - Status of blood pressure calibration (06.11.2019) * it's used in blood pressure measurement - - Blood pressure calibration (09.11.2019) (TODO fix button initiating calibration) + - Blood pressure calibration (09.11.2019) Device settings - Lift wrist to screen on [on, off,TODO scheduled] (02.11.2019) @@ -68,10 +67,12 @@ WORK PROGRESS - Trad-watch mode -> the watch only works as an analog one - Do not disturb [on, off, scheduled] (10.11.2019) (need reconnect to apply) - Send User details to watch [height, weight, age, gender] (10.11.2019) (need more testing) + - Implemented long sit reminder (inactivity reminder)[on, off, period] (17.11.2019) + - Set watch language [English, Chinese] (17.11.2019) Activity data - get steps per day - - get heart reate measurements + - get heart rate measurements - get sleep data - set user goal for steps 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 1fd5186a2..bb6eb6f7b 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 @@ -17,10 +17,12 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus; +import android.app.AlertDialog; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; @@ -29,6 +31,7 @@ import android.os.Handler; import android.widget.Toast; import androidx.annotation.IntRange; +import androidx.annotation.MainThread; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -51,6 +54,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.DebugActivity; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; @@ -155,7 +159,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } catch (IOException e) { e.printStackTrace(); } - return builder; } @@ -185,15 +188,17 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_DEFAULT, message); } -// cancel notification -// cancel watch notification - stop vibration and turn off screen + /** Cancel notification + * cancel watch notification - stop vibration and turn off screen + * on watch - clear phone icon near bluetooth + */ private void cancelNotification() { try { getQueue().clear(); TransactionBuilder builder = performInitialized("cancelNotification"); byte[] bArr; - int mPosition = 1024; - int mMessageId = 0xFF; + int mPosition = 1024; // all positions + int mMessageId = 0xFF; // all messages bArr = new byte[6]; bArr[0] = (byte) ((int) (mPosition >> 24)); bArr[1] = (byte) ((int) (mPosition >> 16)); @@ -211,6 +216,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** Format text and send it to watch + * @param notificationChannel - text or call + * @param notificationText - text to show + */ private void sendNotification(int notificationChannel, String notificationText) { try { TransactionBuilder builder = performInitialized("showNotification"); @@ -251,7 +260,11 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } - + /** enable notification channels on watch + * @param builder + * enable all notification channels + * TODO add settings to choose notification channels + */ private WatchXPlusDeviceSupport enableNotificationChannels(TransactionBuilder builder) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_NOTIFICATION_SETTINGS, @@ -368,6 +381,9 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** send command to request watch firmware version + * @param builder + */ public WatchXPlusDeviceSupport getFirmwareVersion(TransactionBuilder builder) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_FIRMWARE_INFO, @@ -376,6 +392,9 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } + /** send command to request watch battery state + * @param builder + */ private WatchXPlusDeviceSupport getBatteryState(TransactionBuilder builder) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_BATTERY_INFO, @@ -384,21 +403,24 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } + /** initialize device on connect + * @param builder + */ public WatchXPlusDeviceSupport initialize(TransactionBuilder builder) { getFirmwareVersion(builder) .getBatteryState(builder) .enableNotificationChannels(builder) - .setFitnessGoal(builder) - .getBloodPressureCalibrationStatus(builder) - .syncPreferences(builder); + .setFitnessGoal(builder) // set steps per day + .getBloodPressureCalibrationStatus(builder) // request blood pressure calibration + .syncPreferences(builder); // read preferences from app and set them to watch builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); builder.setGattCallback(this); - return this; } @Override public void onDeleteNotification(int id) { + isMissedCall = false; cancelNotification(); } @@ -450,23 +472,39 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** send notification on watch when phone rings + * @param callSpec - phone state + * send notification on incoming call, cancel notification when call is answered, ignored or rejected + * send missed call notification (if enabled from settings) when phone state changed from ringing to end call + * TODO add missed call reminder (send notification to watch at desired period) + */ // variables to handle ring notifications - boolean isRinging = false; - int remainingRepeats = 0; + boolean isRinging = false; // store ringing state + boolean outCall = false; // store outgoing call state + boolean isMissedCall = false; // missed call state + int remainingRepeats = 0; // initialize call notification reminds + int remainingMissedRepeats = 0; // initialize missed call notification reminds @Override public void onSetCallState(final CallSpec callSpec) { - final int repeatDelay = 5000; // repeat delay of 5 sec - // get settings from device settings page - final boolean continiousRing = WatchXPlusDeviceCoordinator.getContiniousVibrationOnCall(getDevice().getAddress()); + final int repeatDelay = 5000; // repeat delay of 5 sec (watch show call notifications for about 5 sec.) + final int repeatMissedDelay = 60000; // repeat missed call delay of 60 sec + // get settings for continuous vibration while phone rings + final boolean continuousRing = WatchXPlusDeviceCoordinator.getContiniousVibrationOnCall(getDevice().getAddress()); + // set settings for missed call boolean missedCall = WatchXPlusDeviceCoordinator.getMissedCallReminder(getDevice().getAddress()); int repeatCount = WatchXPlusDeviceCoordinator.getRepeatOnCall(getDevice().getAddress()); + int repeatCountMissed = WatchXPlusDeviceCoordinator.getMissedCallRepeat(getDevice().getAddress()); // check if repeatCount is in boundaries min=0, max=10 if (repeatCount < 0) repeatCount = 0; if (repeatCount > 10) repeatCount = 10; // limit repeats to 10 + // check if repeatCountMissed is in boundaries min=0, max=10 + if (repeatCountMissed < 0) repeatCountMissed = 0; + if (repeatCountMissed > 10) repeatCountMissed = 10; // limit repeats to 10 switch (callSpec.command) { case CallSpec.CALL_INCOMING: isRinging = true; + isMissedCall = false; 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 @@ -478,7 +516,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { handler.postDelayed(new Runnable() { public void run() { // Actions to do after repeatDelay seconds - if (((isRinging) && (remainingRepeats > 0)) || ((isRinging) && (continiousRing))) { + if (((isRinging) && (remainingRepeats > 0)) || ((isRinging) && (continuousRing))) { remainingRepeats = remainingRepeats - 1; sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, callSpec.name); // re-run handler @@ -494,41 +532,76 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { break; case CallSpec.CALL_START: isRinging = false; + outCall = false; + isMissedCall = false; cancelNotification(); break; case CallSpec.CALL_REJECT: isRinging = false; + outCall = false; + isMissedCall = false; cancelNotification(); break; case CallSpec.CALL_ACCEPT: isRinging = false; + outCall = false; + isMissedCall = false; cancelNotification(); break; case CallSpec.CALL_OUTGOING: + outCall = true; isRinging = false; + isMissedCall = false; cancelNotification(); break; case CallSpec.CALL_END: - if (isRinging) { + if ((isRinging) && (!outCall)) { // it's a missed call, don't clear notification to preserve small icon near bluetooth isRinging = false; + outCall = false; + isMissedCall = true; + remainingMissedRepeats = repeatCountMissed; // send missed call notification if enabled in settings if (missedCall) { sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "Missed call"); + // repeat missed call notification + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + public void run() { + // Actions to do after repeatDelay seconds + if ((isMissedCall) && (remainingMissedRepeats > 0)) { + remainingMissedRepeats = remainingMissedRepeats - 1; + sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "Missed call"); + // re-run handler + handler.postDelayed(this, repeatMissedDelay); + } else { + remainingMissedRepeats = 0; + isMissedCall = false; + // stop handler + handler.removeCallbacks(this); + } + } + }, repeatMissedDelay); } } else { isRinging = false; + outCall = false; + isMissedCall = false; cancelNotification(); } break; default: isRinging = false; + isMissedCall = false; cancelNotification(); break; } } -// handle button press while ringing + /** handle button press while ringing + * @param value - reply from watch + * while phone rings choose what to do when watch button is pressed + */ private void handleButtonWhenRing(byte[] value) { GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl(); // get saved settings if true - reject call, otherwise ignore call @@ -554,11 +627,17 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { buildCommand(WatchXPlusConstants.CMD_FITNESS_GOAL_SETTINGS, WatchXPlusConstants.WRITE_VALUE, Conversion.toByteArr16(fitnessGoal))); - return this; } -// set personal info + /** set personal info - read it from About me + * @param builder + * @param height - user height in meters + * @param weight - user weight in kg + * @param age - user age + * @param gender - user age + * send personal information on watch + */ private WatchXPlusDeviceSupport setPersonalInformation(TransactionBuilder builder, int height, int weight, int age, int gender) { LOG.warn(" Setting Personal Information... height:"+height+" weight:"+weight+" age:"+age+" gender:"+gender); byte[] command = WatchXPlusConstants.CMD_SET_PERSONAL_INFO; @@ -576,8 +655,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } -// handle get/set personal info -// for test purposes only + /** handle get/set personal info + * @param value - reply from watch + * actual do nothing (for test purposes only) + */ private void handlePersonalInfo(byte[] value) { int height = Conversion.fromByteArr16(value[8]); int weight = Conversion.fromByteArr16(value[9]); @@ -585,6 +666,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { int gender = Conversion.fromByteArr16(value[11]); LOG.info(" Personal info - height:" + height + ", weight:" + weight + ", age:" + age + ", gender:" + gender); } + @Override public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { @@ -724,20 +806,25 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { getDisconnectReminderStatus(builder); break; case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT: - setTimeFormat(builder, sharedPreferences); + setLanguageAndTimeFormat(builder, sharedPreferences); break; case WatchXPlusConstants.PREF_DO_NOT_DISTURB: case WatchXPlusConstants.PREF_DO_NOT_DISTURB_START: case WatchXPlusConstants.PREF_DO_NOT_DISTURB_END: - LOG.info(" bravo "); setQuiteHours(builder, sharedPreferences); break; case "BP_CAL": sendBloodPressureCalibration(); break; + case "LONG_SIT": + setLongSitHours(builder, sharedPreferences); + break; case "WXP_POWER_MODE": setPowerMode(config); break; + case "WXP_LANGUAGE": + setLanguageAndTimeFormat(builder, sharedPreferences); + break; } builder.queue(getQueue()); } catch (IOException e) { @@ -755,26 +842,111 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { requestBloodPressureMeasurement(); } -// set do not disturb time - private WatchXPlusDeviceSupport setQuiteHours(TransactionBuilder tbuilder, boolean enable, int hourStart, int minuteStart, int hourEnd, int minuteEnd) { - LOG.warn(" Setting DND time... Hs:"+hourStart+" Ms:"+minuteStart+" He:"+hourEnd+" Me:"+minuteEnd); - byte[] command = WatchXPlusConstants.CMD_SET_QUITE_HOURS_TIME; - byte[] bArr = new byte[4]; - bArr[0] = (byte) hourStart; // byte[08] - bArr[1] = (byte) minuteStart; // byte[09] - bArr[2] = (byte) hourEnd; // byte[10] - bArr[3] = (byte) minuteEnd; // byte[11] + /** set long sit reminder time + * @param builder + * @param enable - state (true - enabled or false - disabled) + * @param hourStart - begin hour + * @param minuteStart - begin minute + * @param hourEnd - end hour + * @param minuteEnd - end minute + * set long sit reminder (inactivity reminder) on watch + */ + private WatchXPlusDeviceSupport setLongSitHours(TransactionBuilder builder, boolean enable, int hourStart, int minuteStart, int hourEnd, int minuteEnd, int period) { + LOG.warn(" Setting Long sit reminder... Enabled:"+enable+" Period:"+period); + LOG.warn(" Setting Long sit time... Hs:"+hourEnd+" Ms:"+minuteEnd+" He:"+hourStart+" Me:"+minuteStart); + LOG.warn(" Setting Long sit DND time... Hs:"+hourStart+" Ms:"+minuteStart+" He:"+hourEnd+" Me:"+minuteEnd); + // set Long Sit reminder time + byte[] command = WatchXPlusConstants.CMD_INACTIVITY_REMINDER_SET; - tbuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_SET_QUITE_HOURS_TIME, - WatchXPlusConstants.WRITE_VALUE, - bArr)); - setQuiteHoursSwitch(tbuilder, enable); + byte[] bArr = new byte[10]; + bArr[0] = (byte) hourEnd; // byte[08] + bArr[1] = (byte) minuteEnd; // byte[09] + bArr[2] = (byte) hourStart; // byte[10] + bArr[3] = (byte) minuteStart; // byte[11] + bArr[4] = (byte) hourStart; // byte[12] + bArr[5] = (byte) minuteStart; // byte[13] + bArr[6] = (byte) hourEnd; // byte[14] + bArr[7] = (byte) minuteEnd; // byte[15] + bArr[8] = (byte) (period >> 8); // byte[16] + bArr[9] = (byte) period; // byte[17] + + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, WatchXPlusConstants.WRITE_VALUE, bArr)); + // set long sit reminder state (enabled, disabled) + setLongSitSwitch(builder, enable); return this; } - // set do not disturb switch + /** get Long sit settings from app, and send it to watch + * @param builder + * @param sharedPreferences + * @return + */ + private WatchXPlusDeviceSupport setLongSitHours(TransactionBuilder builder, SharedPreferences sharedPreferences) { + Calendar start = new GregorianCalendar(); + Calendar end = new GregorianCalendar(); + boolean enable = WatchXPlusDeviceCoordinator.getLongSitHours(sharedPreferences, start, end); + if (enable) { + int period = prefs.getInt(WatchXPlusConstants.PREF_LONGSIT_PERIOD, 60); + return this.setLongSitHours(builder, enable, + start.get(Calendar.HOUR_OF_DAY), start.get(Calendar.MINUTE), + end.get(Calendar.HOUR_OF_DAY), end.get(Calendar.MINUTE), + period); + } else { + // disable Long sit reminder + LOG.info(" Long sit reminder are disabled"); + return this.setLongSitSwitch(builder, enable); + } + } + + /** set long sit reminder switch + * @param tbuilder + * @param enable - true or false + * enabled or disables long sit reminder (inactivity reminder) on watch + */ + private WatchXPlusDeviceSupport setLongSitSwitch(TransactionBuilder tbuilder, boolean enable) { + LOG.warn("Setting Long sit reminder switch to" + enable); + tbuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_INACTIVITY_REMINDER_SWITCH, + WatchXPlusConstants.WRITE_VALUE, + new byte[]{(byte) (enable ? 0x01 : 0x00)})); + return this; + } + + + + /** set do not disturb time + * @param builder + * @param enable - state (true - enabled or false - disabled) + * @param hourStart - begin hour + * @param minuteStart - begin minute + * @param hourEnd - end hour + * @param minuteEnd - end minute + * set do not disturb on watch + */ + private WatchXPlusDeviceSupport setQuiteHours(TransactionBuilder builder, boolean enable, int hourStart, int minuteStart, int hourEnd, int minuteEnd) { + LOG.warn(" Setting DND time... Hs:"+hourStart+" Ms:"+minuteStart+" He:"+hourEnd+" Me:"+minuteEnd); + // set DND time + byte[] command = WatchXPlusConstants.CMD_SET_QUITE_HOURS_TIME; + + byte[] bArr = new byte[4]; + bArr[0] = (byte) hourStart; // byte[08] + bArr[1] = (byte) minuteStart; // byte[09] + bArr[2] = (byte) hourEnd; // byte[10] + bArr[3] = (byte) minuteEnd; // byte[11] + builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(command, WatchXPlusConstants.WRITE_VALUE, bArr)); + // set DND state (enabled, disabled) + setQuiteHoursSwitch(builder, enable); + return this; + } + + /** set do not disturb switch + * @param tbuilder + * @param enable - true or false + * enabled or disables DND on watch + */ private WatchXPlusDeviceSupport setQuiteHoursSwitch(TransactionBuilder tbuilder, boolean enable) { LOG.warn("Setting DND switch to" + enable); tbuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), @@ -784,6 +956,11 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } + /** get DND settings from app, and send it to watch + * @param builder + * @param sharedPreferences + * @return + */ private WatchXPlusDeviceSupport setQuiteHours(TransactionBuilder builder, SharedPreferences sharedPreferences) { Calendar start = new GregorianCalendar(); Calendar end = new GregorianCalendar(); @@ -793,20 +970,22 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { start.get(Calendar.HOUR_OF_DAY), start.get(Calendar.MINUTE), end.get(Calendar.HOUR_OF_DAY), end.get(Calendar.MINUTE)); } else { + // disable DND LOG.info(" Quiet hours are disabled"); return this.setQuiteHoursSwitch(builder, enable); } } -// set watch power mode + /** set watch power + * @param config + * switch watch power mode + * modes (0- normal, 1- energysaving, 2- only watch) + */ private WatchXPlusDeviceSupport setPowerMode(String config) { int settingRead = prefs.getInt("wxp_power_mode", 0); byte[] bArr = new byte[1]; - if (settingRead == 0) bArr[0] = 0x00; - if (settingRead == 1) bArr[0] = 0x01; - if (settingRead == 2) bArr[0] = 0x02; - LOG.info(" setting: " + config + " mode: " + bArr[0]); - + bArr[0] = (byte) settingRead; + LOG.info(" setting: " + config); try { TransactionBuilder builder = performInitialized("setPowerMode"); builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), @@ -820,7 +999,9 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } -// check status of blood pressure calibration + /** request status of blood pressure calibration + * @param builder + */ private WatchXPlusDeviceSupport getBloodPressureCalibrationStatus(TransactionBuilder builder) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_IS_BP_CALIBRATED, @@ -829,20 +1010,25 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } - CoordinatorLayout coordinatorLayout; - -// check status of blood pressure calibration + /** send blood pressure calibration to watch + * TODO add better error handling if blood pressure calibration is failed + */ private WatchXPlusDeviceSupport sendBloodPressureCalibration() { try { + int beginCalibration = prefs.getInt(WatchXPlusConstants.PREF_BP_CAL_SWITCH, 0); + if (beginCalibration == 1) { + LOG.warn(" Calibrating BP - cancel " + beginCalibration); + return this; + } int mLowP = prefs.getInt(WatchXPlusConstants.PREF_BP_CAL_LOW, 80); int mHighP = prefs.getInt(WatchXPlusConstants.PREF_BP_CAL_HIGH, 130); - LOG.warn("Calibrating BP ... LowP=" + mLowP + " HighP="+mHighP); + LOG.warn(" Calibrating BP ... LowP=" + mLowP + " HighP="+mHighP); GB.toast("Calibrating BP...", Toast.LENGTH_LONG, GB.INFO); TransactionBuilder builder = performInitialized("bpCalibrate"); byte[] command = WatchXPlusConstants.CMD_BP_CALIBRATION; - byte mStart = 0x01; + byte mStart = 0x01; // initiate calibration byte[] bArr = new byte[5]; bArr[0] = (byte) mStart; // byte[08] @@ -862,6 +1048,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } + /** handle watch response if blood pressure is calibrated + * @param value - watch response + * save result to global variable (uses for BP measurement) + */ private void handleBloodPressureCalibrationStatus(byte[] value) { if (Conversion.fromByteArr16(value[8]) != 0) { WatchXPlusDeviceCoordinator.isBPCalibrated = false; @@ -870,7 +1060,9 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } - + /** handle watch response for result of blood pressure calibration + * @param value - watch response + */ private void handleBloodPressureCalibrationResult(byte[] value) { if (Conversion.fromByteArr16(value[8]) != 0x00) { WatchXPlusDeviceCoordinator.isBPCalibrated = false; @@ -882,8 +1074,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { GB.toast("OK. Measured Low:"+low+" high:"+high, Toast.LENGTH_LONG, GB.INFO); } } -// end check status of blood pressure calibration + /** request blood pressure measurement + * first check if blood pressure is calibrated + */ private void requestBloodPressureMeasurement() { if (!WatchXPlusDeviceCoordinator.isBPCalibrated) { LOG.warn("BP is NOT calibrated"); @@ -1021,7 +1215,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { handleContentDataChunk(value); return true; } else { - LOG.info(" Unhandled characteristic changed: " + characteristicUUID); + LOG.info(" Unhandled characteristic changed: " + characteristicUUID + " value " + value); logMessageContent(characteristic.getValue()); } @@ -1303,6 +1497,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return result; } + /** handle watch response for steps goal (show steps setting) + * @param value - watch reply + * for test purposes only + */ private void handleSportAimStatus(byte[] value) { int stepsAim = Conversion.fromByteArr16(value[8], value[9]); LOG.debug(" Received goal stepsAim: " + stepsAim); @@ -1440,19 +1638,27 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return (byte) (checksum & 0xFF); } + /** handle watch response for firmware version + * @param value - watch response + */ private void handleFirmwareInfo(byte[] value) { versionInfo.fwVersion = String.format(Locale.US, "%d.%d.%d", value[8], value[9], value[10]); handleGBDeviceEvent(versionInfo); } + /** handle watch response for battery level + * @param value + */ private void handleBatteryState(byte[] value) { batteryInfo.state = value[8] == 1 ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_LOW; batteryInfo.level = value[9]; handleGBDeviceEvent(batteryInfo); } -// handle lift wrist to screen on and shake to refuse call -// for test purposes only + /** handle watch response for lift wrist, and shake to refuse/ignore call + * @param value - watch response + * for test purposes only + */ private void handleShakeState(byte[] value) { boolean z = true; String light = "lightScreen"; @@ -1471,18 +1677,17 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { LOG.info(" handleShakeState: " + light + " " + refuse); } -// handle disconnect reminder state -// for test purposes only + /** handle disconnect reminder (lost device) status + * @param value - watch response + * for test purposes only + */ private void handleDisconnectReminderState(byte[] value) { boolean z = true; if (1 != value[8]) { z = false; } LOG.info(" disconnectReminder: " + Boolean.valueOf(z) + " val: " + value[8]); - return; - - } // read preferences @@ -1491,8 +1696,9 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { this.setHeadsUpScreen(transaction, sharedPreferences); // lift wirst to screen on this.setQuiteHours(transaction, sharedPreferences); // DND this.setDisconnectReminder(transaction, sharedPreferences); // disconnect reminder - this.setTimeFormat(transaction, sharedPreferences); // set time mode 12/24h + this.setLanguageAndTimeFormat(transaction, sharedPreferences); // set time mode 12/24h this.setAltitude(transaction); // set altitude calibration + this.setLongSitHours(transaction, sharedPreferences); // set Long sit reminder ActivityUser activityUser = new ActivityUser(); this.setPersonalInformation(transaction, activityUser.getHeightCm(), activityUser.getWeightKg(), activityUser.getAge(),activityUser.getGender()); @@ -1589,18 +1795,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } -// set time format - private WatchXPlusDeviceSupport setTimeFormat(TransactionBuilder transactionBuilder, byte timeMode) { - byte[] bArr = new byte[2]; - bArr[0] = 0x01; //byte[08] language - force to English language - bArr[1] = timeMode; //byte[09] time - transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand(WatchXPlusConstants.CMD_TIME_LANGUAGE, - WatchXPlusConstants.WRITE_VALUE, - bArr)); - return this; - } - // calibrate altitude private WatchXPlusDeviceSupport setAltitude(TransactionBuilder transactionBuilder) { int value = WatchXPlusDeviceCoordinator.getAltitude(getDevice().getAddress()); @@ -1622,9 +1816,22 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } - private WatchXPlusDeviceSupport setTimeFormat(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { - return this.setTimeFormat(transactionBuilder, - WatchXPlusDeviceCoordinator.getTimeMode(sharedPreferences)); + // set time format + private WatchXPlusDeviceSupport setLanguageAndTimeFormat(TransactionBuilder transactionBuilder, byte timeMode, byte language) { + byte[] bArr = new byte[2]; + bArr[0] = language; //byte[08] language + bArr[1] = timeMode; //byte[09] time + transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), + buildCommand(WatchXPlusConstants.CMD_TIME_LANGUAGE, + WatchXPlusConstants.WRITE_VALUE, + bArr)); + return this; + } + + private WatchXPlusDeviceSupport setLanguageAndTimeFormat(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { + return this.setLanguageAndTimeFormat(transactionBuilder, + WatchXPlusDeviceCoordinator.getTimeMode(sharedPreferences), + WatchXPlusDeviceCoordinator.getLanguage(sharedPreferences)); } @Override diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 920f0e19d..4d2c5ee94 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -283,23 +283,41 @@ Сверяване Watch 9 свързване Watch 9 сверяване - WatchX Plus сверяване + + Watch X Plus сверяване Единици Формат на часа - Калибриране на височина - Повтори известия за звънене + Калибриране на височината + Повтаряй известие за позвъняване Възможни стойности min=0, max=10 Известявай докато телефона звъни - Изкл. - игнорира, Вкл. - отказ + Известие за пропуснато обаждане + Watch X Plus настройки + Настройки на известията + Изкл. - заглуши, Вкл. - откажи + Бутона заглушава/отказва повикване + Повтаря действието на бутона + Разклати за заглушаване/отказване на повикването Калибриране на кръвно налягане - Кръвно налягане DIASTOLIC (долна) - Кръвно налягане SYSTOLIC (горна) - Бутона игнорира/отказва повикване - Дублира действието на бутона - Разклащането игнорира/отказва повикване - Известие за пропуснато повикване - WatchXPlus настройки - WatchXPlus калибриране + Кръвно налягане DIASTOLIC (ниска) + Кръвно налягане SYSTOLIC (висока) + Калибриране + Натисни тук за калибриране + Калибриране на сензорите + Настройки на устройството + Режим на часовника + Нормален + Икономичен + Само часовник + Повтаряй известие за пропуснато повикване всяка минута за X пъти + Повтаряй известието за пропуснато повикване + Напомняне за бездействие + Напомняй ако няма активност за повече от X минути + Времевия интервал е от настройката за DND + Включи напомняне за активност + Период на неактивност (минути) + Език + Наблюдение/анализ на съня Съхраняване на log файлове Инициализиране @@ -537,10 +555,6 @@ Начало Не безпокой Вие спахте от %1$s до %2$s - Режим на часовника - Само часовник - Икономичен - Нормален Стъпки: %1$02d Сън: %1$s 5 минути @@ -562,13 +576,10 @@ Избери всички Сподели Запази настройките - Натисни тук за старт на калибрирането - Започни калибриране Горна граница Долна граница От ляво на дясно Аларми - WatchXPlus настройки метрични инчови крачки diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index c766f389d..d88b7c361 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -69,6 +69,22 @@ never + + @string/simplified_chinese + @string/english + + + 0 + 1 + + + @string/prefs_wxp_button_bp_calibration + @string/Cancel + + + 0 + 1 + @string/wxp_mode_normal @string/wxp_mode_saving diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2dd8fb0ff..8740ed001 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -187,6 +187,7 @@ All day heart rate measurement HPlus/Makibes settings + Watch X Plus calibration Units Time format Altitude calibration @@ -194,7 +195,8 @@ Possible values min=0, max=10 Vibration during phone ring Vibration on missed call - WatchXPlus settings + Watch X Plus settings + Notification settings Off - ignore, On - reject Button ignore/reject call Duplicates watch button action @@ -202,14 +204,22 @@ Blood Pressure calibration Blood Pressure DIASTOLIC (low) Blood Pressure SYSTOLIC (high) - Begin calibration + Calibration Press here to begin calibration - WatchXPlus calibration - WatchXPlus settings + Sensors Calibration + Device settings Watch mode Normal Power saving Only watch + Repeat missed call notification every minute for X times + Repeat missed call notification + Inactivity reminder + Remind if there is no activity for more than X minutes + Inactivity time interval is from DND setting + Enable inactivity reminder + Inactivity period (minutes) + Language Makibes HR3 settings @@ -725,7 +735,6 @@ Calibrate Watch 9 pairing Watch 9 calibration - WatchX Plus calibration Contextual Arabic Enable this to support contextual Arabic Right To Left Support diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index c84357aee..f49005db3 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -581,7 +581,7 @@ android:title="@string/preferences_watchxplus_settings"> + android:title="@string/pref_header_wxp_notification"> + + + + + + + - + Makibes HR3 settings diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index c8c0f3f0b..7839cd3a5 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -575,134 +575,10 @@ android:key="pref_key_zetime" android:title="@string/zetime_title_settings"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:title="@string/preferences_watchxplus_settings"/> diff --git a/app/src/main/res/xml/watchxplus_preferences.xml b/app/src/main/res/xml/watchxplus_preferences.xml new file mode 100644 index 000000000..68348e7d7 --- /dev/null +++ b/app/src/main/res/xml/watchxplus_preferences.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 0cffd0f54c168341247d62f3d8925dac00345a7f Mon Sep 17 00:00:00 2001 From: mamutcho Date: Tue, 19 Nov 2019 20:53:19 +0200 Subject: [PATCH 028/546] Bump Gadgetbridge version to 0.39 Redesign Settings page Ready to pull request --- .../gadgetbridge/devices/lenovo/watchxplus/WorkProgress | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress index 737a398ea..f93f95330 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WorkProgress @@ -25,7 +25,8 @@ NEED TO BE DONE WORK PROGRESS - Bump version to 0.39 (19.11.2019) + Bump Gadgetbridge version to 0.39 (19.11.2019) + Send notification to watch - On incoming call - add function to cancel notification on watch (04.11.2019) From e03fd4357e4514ef572d65e86a75a615658ebac1 Mon Sep 17 00:00:00 2001 From: mamutcho Date: Wed, 20 Nov 2019 20:38:02 +0200 Subject: [PATCH 029/546] Add initial Watch X non-plus support --- .../lenovo/watchxplus/WatchXPlusDeviceCoordinator.java | 4 ++++ .../gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java | 3 ++- app/src/main/res/xml/watchxplus_preferences.xml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) 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 977ba302d..bff49a6d7 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 @@ -79,6 +79,10 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { return DeviceType.WATCHXPLUS; } else if (deviceName.equalsIgnoreCase("WATCH XPLUS")) { return DeviceType.WATCHXPLUS; + // add initial support for Watch X non-plus (forces Watch X to be recognized as Watch XPlus) + // Watch X non-plus have same MAC address as Watch 9 (starts with "1C:87:79") + } else if (deviceName.equalsIgnoreCase("WATCH X")) { + return DeviceType.WATCHXPLUS; } return DeviceType.UNKNOWN; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java index 18b0a8844..08d8d1d04 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/watch9/Watch9DeviceCoordinator.java @@ -63,7 +63,8 @@ public class Watch9DeviceCoordinator extends AbstractDeviceCoordinator { String deviceName = candidate.getName().toUpperCase(); if (candidate.supportsService(Watch9Constants.UUID_SERVICE_WATCH9)) { return DeviceType.WATCH9; - } else if (macAddress.startsWith("1C:87:79")) { + // add support for Watch X non-plus (same MAC address) + } else if ((macAddress.startsWith("1C:87:79")) && (!deviceName.equalsIgnoreCase("WATCH X"))) { return DeviceType.WATCH9; } else if (deviceName.equals("WATCH 9")) { return DeviceType.WATCH9; diff --git a/app/src/main/res/xml/watchxplus_preferences.xml b/app/src/main/res/xml/watchxplus_preferences.xml index 68348e7d7..7be6108e9 100644 --- a/app/src/main/res/xml/watchxplus_preferences.xml +++ b/app/src/main/res/xml/watchxplus_preferences.xml @@ -39,7 +39,7 @@ android:key="pref_category_watchxplus_callhandling" android:title="@string/pref_header_wxp_notification_callhandling"> + android:title="@string/pref_header_wxp_notification_callhandling"/> Date: Wed, 11 Dec 2019 19:38:12 +0200 Subject: [PATCH 030/546] Bug fixes --- .../WatchXPlusDeviceCoordinator.java | 26 +- .../WatchXPlusPreferenceActivity.java | 1 - .../watchxplus/WatchXPlusSampleProvider.java | 5 - .../watchxplus/WatchXPlusDeviceSupport.java | 308 ++++++++++++------ .../service/devices/xwatch/XWatchSupport.java | 3 +- 5 files changed, 220 insertions(+), 123 deletions(-) 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 bff49a6d7..aff51c4bc 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 @@ -43,11 +43,11 @@ import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext; public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { private static final Logger LOG = LoggerFactory.getLogger(WatchXPlusDeviceSupport.class); - public static final int FindPhone_ON = -1; + private static final int FindPhone_ON = -1; public static final int FindPhone_OFF = 0; public static boolean isBPCalibrated = false; - protected static Prefs prefs = GBApplication.getPrefs(); + private static Prefs prefs = GBApplication.getPrefs(); @NonNull @Override @@ -59,7 +59,7 @@ public class WatchXPlusDeviceCoordinator extends AbstractDeviceCoordinator { } @Override - protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) { } @@ -185,6 +185,7 @@ Prefs from device settings on main page // return time format pref public static byte getTimeMode(SharedPreferences sharedPrefs) { String timeMode = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h)); + assert timeMode != null; if (timeMode.equals(getContext().getString(R.string.p_timeformat_24h))) { return WatchXPlusConstants.ARG_SET_TIMEMODE_24H; } else { @@ -202,6 +203,7 @@ Prefs from device settings on main page public static boolean shouldEnableHeadsUpScreen(SharedPreferences sharedPrefs) { String liftMode = sharedPrefs.getString(WatchXPlusConstants.PREF_ACTIVATE_DISPLAY, getContext().getString(R.string.p_on)); // WatchXPlus doesn't support scheduled intervals. Treat it as "on". + assert liftMode != null; return !liftMode.equals(getContext().getString(R.string.p_off)); } @@ -209,6 +211,7 @@ Prefs from device settings on main page public static boolean shouldEnableDisconnectReminder(SharedPreferences sharedPrefs) { String lostReminder = sharedPrefs.getString(WatchXPlusConstants.PREF_DISCONNECT_REMIND, getContext().getString(R.string.p_on)); // WatchXPlus doesn't support scheduled intervals. Treat it as "on". + assert lostReminder != null; return !lostReminder.equals(getContext().getString(R.string.p_off)); } @@ -219,6 +222,7 @@ Prefs from device settings on main page public static int getFindPhone(SharedPreferences sharedPrefs) { String findPhone = sharedPrefs.getString(WatchXPlusConstants.PREF_FIND_PHONE, getContext().getString(R.string.p_off)); + assert findPhone != null; if (findPhone.equals(getContext().getString(R.string.p_off))) { return FindPhone_OFF; } else if (findPhone.equals(getContext().getString(R.string.p_on))) { @@ -230,6 +234,7 @@ Prefs from device settings on main page int iDuration; try { + assert duration != null; iDuration = Integer.valueOf(duration); } catch (Exception ex) { iDuration = 60; @@ -250,6 +255,7 @@ Prefs from device settings on main page public static boolean getQuiteHours(SharedPreferences sharedPrefs, Calendar startOut, Calendar endOut) { String doNotDisturb = sharedPrefs.getString(WatchXPlusConstants.PREF_DO_NOT_DISTURB, getContext().getString(R.string.p_off)); + assert doNotDisturb != null; if (doNotDisturb.equals(getContext().getString(R.string.p_off))) { LOG.info(" DND is disabled "); return false; @@ -303,38 +309,38 @@ Values from device specific settings page */ // read altitude from preferences public static int getAltitude(String address) { - return (int) prefs.getInt(WatchXPlusConstants.PREF_ALTITUDE, 200); + return prefs.getInt(WatchXPlusConstants.PREF_ALTITUDE, 200); } // read repeat call notification public static int getRepeatOnCall(String address) { - return (int) prefs.getInt(WatchXPlusConstants.PREF_REPEAT, 1); + return prefs.getInt(WatchXPlusConstants.PREF_REPEAT, 1); } //read continious call notification public static boolean getContiniousVibrationOnCall(String address) { - return (boolean) prefs.getBoolean(WatchXPlusConstants.PREF_CONTINIOUS, false); + return 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); + return prefs.getBoolean(WatchXPlusConstants.PREF_MISSED_CALL, false); } //read missed call notification public static int getMissedCallRepeat(String address) { - return (int) prefs.getInt(WatchXPlusConstants.PREF_MISSED_CALL_REPEAT, 0); + return prefs.getInt(WatchXPlusConstants.PREF_MISSED_CALL_REPEAT, 0); } //read button reject call settings public static boolean getButtonReject(String address) { - return (boolean) prefs.getBoolean(WatchXPlusConstants.PREF_BUTTON_REJECT, false); + return 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); + return prefs.getBoolean(WatchXPlusConstants.PREF_SHAKE_REJECT, false); } /* diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusPreferenceActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusPreferenceActivity.java index da476362e..46ef967e6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusPreferenceActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusPreferenceActivity.java @@ -23,7 +23,6 @@ import android.preference.Preference; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity; -import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; public class WatchXPlusPreferenceActivity extends AbstractSettingsActivity { @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java index 0c596ab0c..cb9bfeba1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lenovo/watchxplus/WatchXPlusSampleProvider.java @@ -13,14 +13,9 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; public class WatchXPlusSampleProvider extends AbstractSampleProvider { - private GBDevice mDevice; - private DaoSession mSession; - public WatchXPlusSampleProvider(GBDevice device, DaoSession session) { super(device, session); - mSession = session; - mDevice = device; } @Override 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 7059d7f37..33d882e9c 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 @@ -17,12 +17,10 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.lenovo.watchxplus; -import android.app.AlertDialog; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; @@ -31,14 +29,13 @@ import android.os.Handler; import android.widget.Toast; import androidx.annotation.IntRange; -import androidx.annotation.MainThread; -import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -50,13 +47,9 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; import java.util.UUID; -import java.util.prefs.PreferenceChangeEvent; -import java.util.prefs.PreferenceChangeListener; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.activities.DebugActivity; import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; @@ -97,17 +90,15 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; -import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext; - public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { - protected static Prefs prefs = GBApplication.getPrefs(); + private static Prefs prefs = GBApplication.getPrefs(); private boolean needsAuth; private int sequenceNumber = 0; private boolean isCalibrationActive = false; - private Map dataToFetch = new LinkedHashMap<>(); + private final Map dataToFetch = new LinkedHashMap<>(); private int requestedDataTimestamp; private int dataSlots = 0; private DataType currentDataType; @@ -123,6 +114,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onReceive(Context context, Intent intent) { String broadcastAction = intent.getAction(); + assert broadcastAction != null; switch (broadcastAction) { case WatchXPlusConstants.ACTION_CALIBRATION: enableCalibration(intent.getBooleanExtra(WatchXPlusConstants.ACTION_ENABLE, false)); @@ -230,7 +222,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { try { TransactionBuilder builder = performInitialized("showNotification"); byte[] command = WatchXPlusConstants.CMD_NOTIFICATION_TEXT_TASK; - byte[] text = notificationText.getBytes("UTF-8"); + byte[] text = notificationText.getBytes(StandardCharsets.UTF_8); byte[] messagePart; int messageLength = text.length; @@ -280,13 +272,12 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } - public WatchXPlusDeviceSupport authorizationRequest(TransactionBuilder builder, boolean firstConnect) { + public void authorizationRequest(TransactionBuilder builder, boolean firstConnect) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_AUTHORIZATION_TASK, WatchXPlusConstants.TASK, new byte[]{(byte) (firstConnect ? 0x00 : 0x01)})); //possibly not the correct meaning - return this; } private void enableCalibration(boolean enable) { @@ -388,9 +379,9 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } /** send command to request watch firmware version - * @param builder + * @param builder - transaction builder */ - public WatchXPlusDeviceSupport getFirmwareVersion(TransactionBuilder builder) { + private WatchXPlusDeviceSupport getFirmwareVersion(TransactionBuilder builder) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_FIRMWARE_INFO, WatchXPlusConstants.READ_VALUE)); @@ -399,7 +390,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } /** send command to request watch battery state - * @param builder + * @param builder - transaction builder */ private WatchXPlusDeviceSupport getBatteryState(TransactionBuilder builder) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), @@ -410,7 +401,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } /** initialize device on connect - * @param builder + * @param builder - transaction builder */ public WatchXPlusDeviceSupport initialize(TransactionBuilder builder) { getFirmwareVersion(builder) @@ -479,6 +470,12 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } + private boolean isRinging = false; // store ringing state + private boolean outCall = false; // store outgoing call state + private boolean isMissedCall = false; // missed call state + private int remainingRepeats = 0; // initialize call notification reminds + private int remainingMissedRepeats = 0; // initialize missed call notification reminds + /** send notification on watch when phone rings * @param callSpec - phone state * send notification on incoming call, cancel notification when call is answered, ignored or rejected @@ -486,11 +483,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { * TODO add missed call reminder (send notification to watch at desired period) */ // variables to handle ring notifications - boolean isRinging = false; // store ringing state - boolean outCall = false; // store outgoing call state - boolean isMissedCall = false; // missed call state - int remainingRepeats = 0; // initialize call notification reminds - int remainingMissedRepeats = 0; // initialize missed call notification reminds + @Override public void onSetCallState(final CallSpec callSpec) { final int repeatDelay = 5000; // repeat delay of 5 sec (watch show call notifications for about 5 sec.) @@ -513,6 +506,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { isRinging = true; isMissedCall = false; remainingRepeats = repeatCount; + LOG.info(" Incomming call "); 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 { @@ -532,6 +526,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { remainingRepeats = 0; // stop handler handler.removeCallbacks(this); + cancelNotification(); } } }, repeatDelay); @@ -542,27 +537,32 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { outCall = false; isMissedCall = false; cancelNotification(); + LOG.info(" Call start "); break; case CallSpec.CALL_REJECT: isRinging = false; outCall = false; isMissedCall = false; cancelNotification(); + LOG.info(" Call reject "); break; case CallSpec.CALL_ACCEPT: isRinging = false; outCall = false; isMissedCall = false; cancelNotification(); + LOG.info(" Call accept "); break; case CallSpec.CALL_OUTGOING: outCall = true; isRinging = false; isMissedCall = false; cancelNotification(); + LOG.info(" Outgoing call "); break; case CallSpec.CALL_END: if ((isRinging) && (!outCall)) { + LOG.info(" End call "); // it's a missed call, don't clear notification to preserve small icon near bluetooth isRinging = false; outCall = false; @@ -570,6 +570,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { remainingMissedRepeats = repeatCountMissed; // send missed call notification if enabled in settings if (missedCall) { + LOG.info(" Missed call "); sendNotification(WatchXPlusConstants.NOTIFICATION_CHANNEL_PHONE_CALL, "Missed call"); // repeat missed call notification final Handler handler = new Handler(); @@ -586,6 +587,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { isMissedCall = false; // stop handler handler.removeCallbacks(this); + cancelNotification(); } } }, repeatMissedDelay); @@ -595,12 +597,14 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { outCall = false; isMissedCall = false; cancelNotification(); + LOG.info(" Outgoing call end "); } break; default: isRinging = false; isMissedCall = false; cancelNotification(); + LOG.info(" Call default "); break; } } @@ -616,12 +620,16 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { if (buttonReject) { LOG.info(" call rejected "); isRinging = false; + remainingRepeats = 0; + isMissedCall = false; callCmd.event = GBDeviceEventCallControl.Event.REJECT; evaluateGBDeviceEvent(callCmd); cancelNotification(); } else { LOG.info(" call ignored "); isRinging = false; + remainingRepeats = 0; + isMissedCall = false; callCmd.event = GBDeviceEventCallControl.Event.IGNORE; evaluateGBDeviceEvent(callCmd); cancelNotification(); @@ -638,14 +646,13 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } /** set personal info - read it from About me - * @param builder + * @param builder - transaction builder * @param height - user height in meters * @param weight - user weight in kg * @param age - user age * @param gender - user age - * send personal information on watch */ - private WatchXPlusDeviceSupport setPersonalInformation(TransactionBuilder builder, int height, int weight, int age, int gender) { + private void setPersonalInformation(TransactionBuilder builder, int height, int weight, int age, int gender) { LOG.warn(" Setting Personal Information... height:"+height+" weight:"+weight+" age:"+age+" gender:"+gender); byte[] command = WatchXPlusConstants.CMD_SET_PERSONAL_INFO; @@ -659,7 +666,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { buildCommand(command, WatchXPlusConstants.WRITE_VALUE, bArr)); - return this; } /** handle get/set personal info @@ -727,7 +733,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onFetchRecordedData(int dataTypes) { - TransactionBuilder builder = null; + TransactionBuilder builder; try { builder = performInitialized("fetchData"); @@ -807,7 +813,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { case ActivityUser.PREF_USER_STEPS_GOAL: setFitnessGoal(builder); break; - // settings from App Settings -> WatchXPlus settings case WatchXPlusConstants.PREF_POWER_MODE: setPowerMode(); @@ -826,7 +831,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { case WatchXPlusConstants.PREF_BP_CAL_SWITCH: sendBloodPressureCalibration(); break; - // settings from device card case WatchXPlusConstants.PREF_ACTIVATE_DISPLAY: setHeadsUpScreen(builder, sharedPreferences); @@ -863,7 +867,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { /** set long sit reminder time - * @param builder + * @param builder - transaction builder * @param enable - state (true - enabled or false - disabled) * @param hourStart - begin hour * @param minuteStart - begin minute @@ -898,29 +902,28 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } /** get Long sit settings from app, and send it to watch - * @param builder - * @param sharedPreferences - * @return + * @param builder - transaction builder + * @param sharedPreferences - shared preferences */ - private WatchXPlusDeviceSupport setLongSitHours(TransactionBuilder builder, SharedPreferences sharedPreferences) { + private void setLongSitHours(TransactionBuilder builder, SharedPreferences sharedPreferences) { Calendar start = new GregorianCalendar(); Calendar end = new GregorianCalendar(); boolean enable = WatchXPlusDeviceCoordinator.getLongSitHours(sharedPreferences, start, end); if (enable) { int period = prefs.getInt(WatchXPlusConstants.PREF_LONGSIT_PERIOD, 60); - return this.setLongSitHours(builder, enable, + this.setLongSitHours(builder, enable, start.get(Calendar.HOUR_OF_DAY), start.get(Calendar.MINUTE), end.get(Calendar.HOUR_OF_DAY), end.get(Calendar.MINUTE), period); } else { // disable Long sit reminder LOG.info(" Long sit reminder are disabled"); - return this.setLongSitSwitch(builder, enable); + this.setLongSitSwitch(builder, enable); } } /** set long sit reminder switch - * @param tbuilder + * @param tbuilder - transaction builder * @param enable - true or false * enabled or disables long sit reminder (inactivity reminder) on watch */ @@ -936,7 +939,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { /** set do not disturb time - * @param builder + * @param builder - transaction builder * @param enable - state (true - enabled or false - disabled) * @param hourStart - begin hour * @param minuteStart - begin minute @@ -962,7 +965,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } /** set do not disturb switch - * @param tbuilder + * @param tbuilder - transaction builder * @param enable - true or false * enabled or disables DND on watch */ @@ -976,22 +979,21 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } /** get DND settings from app, and send it to watch - * @param builder - * @param sharedPreferences - * @return + * @param builder - transaction builder + * @param sharedPreferences - shared preferences */ - private WatchXPlusDeviceSupport setQuiteHours(TransactionBuilder builder, SharedPreferences sharedPreferences) { + private void setQuiteHours(TransactionBuilder builder, SharedPreferences sharedPreferences) { Calendar start = new GregorianCalendar(); Calendar end = new GregorianCalendar(); boolean enable = WatchXPlusDeviceCoordinator.getQuiteHours(sharedPreferences, start, end); if (enable) { - return this.setQuiteHours(builder, enable, + this.setQuiteHours(builder, enable, start.get(Calendar.HOUR_OF_DAY), start.get(Calendar.MINUTE), end.get(Calendar.HOUR_OF_DAY), end.get(Calendar.MINUTE)); } else { // disable DND LOG.info(" Quiet hours are disabled"); - return this.setQuiteHoursSwitch(builder, enable); + this.setQuiteHoursSwitch(builder, enable); } } @@ -999,7 +1001,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { * switch watch power mode * modes (0- normal, 1- energysaving, 2- only watch) */ - private WatchXPlusDeviceSupport setPowerMode() { + private void setPowerMode() { int settingRead = prefs.getInt(WatchXPlusConstants.PREF_POWER_MODE, 0); byte[] bArr = new byte[1]; bArr[0] = (byte) settingRead; @@ -1014,7 +1016,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } catch (IOException e) { LOG.warn("Unable to set power mode", e); } - return this; } /** request watch units @@ -1037,7 +1038,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { /** set watch units * */ - private WatchXPlusDeviceSupport setUnitsSettings() { + private void setUnitsSettings() { int units = 0; if (getContext().getString(R.string.p_unit_metric).equals(units)) { LOG.info(" Changed units: metric"); @@ -1058,11 +1059,10 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } catch (IOException e) { LOG.warn("Unable to set units", e); } - return this; } /** request status of blood pressure calibration - * @param builder + * @param builder - transaction builder */ private WatchXPlusDeviceSupport getBloodPressureCalibrationStatus(TransactionBuilder builder) { builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), @@ -1075,12 +1075,12 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { /** send blood pressure calibration to watch * TODO add better error handling if blood pressure calibration is failed */ - private WatchXPlusDeviceSupport sendBloodPressureCalibration() { + private void sendBloodPressureCalibration() { try { int beginCalibration = prefs.getInt(WatchXPlusConstants.PREF_BP_CAL_SWITCH, 0); if (beginCalibration == 1) { LOG.warn(" Calibrating BP - cancel " + beginCalibration); - return this; + return; } int mLowP = prefs.getInt(WatchXPlusConstants.PREF_BP_CAL_LOW, 80); int mHighP = prefs.getInt(WatchXPlusConstants.PREF_BP_CAL_HIGH, 130); @@ -1093,7 +1093,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { byte mStart = 0x01; // initiate calibration byte[] bArr = new byte[5]; - bArr[0] = (byte) mStart; // byte[08] + bArr[0] = mStart; // byte[08] bArr[1] = (byte) (mHighP >> 8); // byte[09] bArr[2] = (byte) mHighP; // byte[10] bArr[3] = (byte) (mLowP >> 8); // byte[11] @@ -1107,7 +1107,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } catch (IOException e) { LOG.warn("Unable to send BP Calibration", e); } - return this; } /** handle watch response if blood pressure is calibrated @@ -1115,11 +1114,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { * save result to global variable (uses for BP measurement) */ private void handleBloodPressureCalibrationStatus(byte[] value) { - if (Conversion.fromByteArr16(value[8]) != 0) { - WatchXPlusDeviceCoordinator.isBPCalibrated = false; - } else { - WatchXPlusDeviceCoordinator.isBPCalibrated = true; - } + WatchXPlusDeviceCoordinator.isBPCalibrated = Conversion.fromByteArr16(value[8]) == 0; } /** handle watch response for result of blood pressure calibration @@ -1171,7 +1166,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { int second = prefs.getInt("wxp_newcmd_second", 0); byte[] command = new byte[]{(byte) first, (byte) second}; - LOG.info("testing new command " + command); + LOG.info("testing new command " + Arrays.toString(command)); builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(command, WatchXPlusConstants.READ_VALUE)); @@ -1187,27 +1182,139 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { public void onSendWeather(WeatherSpec weatherSpec) { try { TransactionBuilder builder = performInitialized("setWeather"); - int currentTemp = 0; - int todayMinTemp = 0; - int todayMaxTemp = 0; + int currentTemp; + int todayMinTemp; + int todayMaxTemp; byte[] command = WatchXPlusConstants.CMD_WEATHER_SET; byte[] weatherInfo = new byte[5]; - String currentCondition = weatherSpec.currentCondition; + int currentCondition = weatherSpec.currentConditionCode; // set weather icon int currentConditionCode = 0; // 0 is sunny switch (currentCondition) { - case "Overcast clouds": +//Group 2xx: Thunderstorm + case 200: //thunderstorm with light rain: //11d + case 201: //thunderstorm with rain: //11d + case 202: //thunderstorm with heavy rain: //11d + currentConditionCode = 1024; + break; + case 210: //light thunderstorm:: //11d + case 211: //thunderstorm: //11d + case 212: //heavy thunderstorm: //11d + case 221: //ragged thunderstorm: //11d + case 230: //thunderstorm with light drizzle: //11d + case 231: //thunderstorm with drizzle: //11d + case 232: //thunderstorm with heavy drizzle: //11d + currentConditionCode = 1025; + break; +//Group 3xx: Drizzle + case 300: //light intensity drizzle: //09d + case 301: //drizzle: //09d + case 302: //heavy intensity drizzle: //09d + case 310: //light intensity drizzle rain: //09d + case 500: //light rain: //10d + currentConditionCode = 256; + break; + case 311: //drizzle rain: //09d + case 312: //heavy intensity drizzle rain: //09d + case 313: //shower rain and drizzle: //09d + case 314: //heavy shower rain and drizzle: //09d + case 321: //shower drizzle: //09d + case 501: //moderate rain: //10d + currentConditionCode = 1280; + break; +//Group 5xx: Rain + case 511: //freezing rain: //13d + case 520: //light intensity shower rain: //09d + case 521: //shower rain: //09d + case 502: //heavy intensity rain: //10d + case 503: //very heavy rain: //10d + case 504: //extreme rain: //10d + case 522: //heavy intensity shower rain: //09d + case 531: //ragged shower rain: //09d + currentConditionCode = 258; + break; +//Group 6xx: Snow + case 600: //light snow: + case 601: //snow: //[[file:13d.png]] + currentConditionCode = 513; + break; + case 620: //light shower snow: //[[file:13d.png]] + currentConditionCode = 514; + break; + case 602: //heavy snow: //[[file:13d.png]] + case 621: //shower snow: //[[file:13d.png]] + case 622: //heavy shower snow: //[[file:13d.png]] + currentConditionCode = 515; + break; + case 611: //sleet: //[[file:13d.png]] + case 612: //shower sleet: //[[file:13d.png]] + currentConditionCode = 1026; + break; + case 615: //light rain and snow: //[[file:13d.png]] + case 616: //rain and snow: //[[file:13d.png]] + currentConditionCode = 4; + break; +//Group 7xx: Atmosphere + case 741: //fog: //[[file:50d.png]] + case 701: //mist: //[[file:50d.png]] + case 711: //smoke: //[[file:50d.png]] + currentConditionCode = 5; + break; + case 721: //haze: //[[file:50d.png]] + currentConditionCode = 3; + break; + case 731: //sandcase dust whirls: //[[file:50d.png]] + currentConditionCode = 771; + break; + case 751: //sand: //[[file:50d.png]] + case 761: //dust: //[[file:50d.png]] + case 762: //volcanic ash: //[[file:50d.png]] + case 771: //squalls: //[[file:50d.png]] + currentConditionCode = 769; + break; + case 781: //tornado: //[[file:50d.png]] + case 900: //tornado + currentConditionCode = 1283; + break; +//Group 800: Clear + case 800: //clear sky + currentConditionCode = 0; + break; +//Group 80x: Clouds + case 801: //few clouds: //[[file:02d.png]] [[file:02n.png]] + case 802: //scattered clouds: //[[file:03d.png]] [[file:03d.png]] + case 803: //broken clouds: //[[file:04d.png]] [[file:03d.png]] currentConditionCode = 1; - case "Broken clouds": + break; + case 804: //overcast clouds: //[[file:04d.png]] [[file:04d.png]] currentConditionCode = 2; break; +//Group 90x: Extreme + case 901: //tropical storm + case 903: //cold + case 904: //hot + case 905: //windy + case 906: //hail + currentConditionCode = 1027; + break; +//Group 9xx: Additional + case 951: //calm + case 952: //light breeze + case 953: //gentle breeze + case 954: //moderate breeze + case 955: //fresh breeze + case 956: //strong breeze + case 957: //high windcase near gale + case 958: //gale + case 959: //severe gale + case 960: //storm + case 961: //violent storm + case 902: //hurricane + case 962: //hurricane + currentConditionCode = 261; + break; } - if ((currentCondition.contains("Fog")) || (currentCondition.contains("fog"))) { currentConditionCode = 3; } - if ((currentCondition.contains("Rain")) || (currentCondition.contains("rain"))) { currentConditionCode = 257; } - if ((currentCondition.contains("Snow")) || (currentCondition.contains("snow"))) { currentConditionCode = 514; } - if ((currentCondition.contains("Sand")) || (currentCondition.contains("sand"))) { currentConditionCode = 769; } - if ((currentCondition.contains("Dust")) || (currentCondition.contains("dust"))) { currentConditionCode = 769; } - if ((currentCondition.contains("Sleet")) || (currentCondition.contains("sleet"))) { currentConditionCode = 1026; } + LOG.info( "Weather cond: " + currentCondition + " icon: " + currentConditionCode); // calculate for temps under 0 currentTemp = (Math.abs(weatherSpec.currentTemp)) - 273; if (currentTemp < 0) { @@ -1304,11 +1411,11 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return true; } else if (WatchXPlusConstants.UUID_CHARACTERISTIC_DATABASE_READ.equals(characteristicUUID)) { - LOG.info(" Value change for characteristic DATABASE: " + characteristicUUID + " value " + value); + LOG.info(" Value change for characteristic DATABASE: " + characteristicUUID + " value " + Arrays.toString(value)); handleContentDataChunk(value); return true; } else { - LOG.info(" Unhandled characteristic changed: " + characteristicUUID + " value " + value); + LOG.info(" Unhandled characteristic changed: " + characteristicUUID + " value " + Arrays.toString(value)); logMessageContent(characteristic.getValue()); } @@ -1516,10 +1623,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } else { LOG.warn(" Got unsupported data package type: " + type); } - } catch (GBException ex) { - LOG.info((ex.getMessage())); } catch (Exception ex) { - LOG.info(ex.getMessage()); + LOG.info((ex.getMessage())); } } @@ -1569,7 +1674,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { TransactionBuilder builder = performInitialized("handleAck"); builder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), - buildCommand((byte) 0x00)); + buildCommand()); builder.queue(getQueue()); } catch (IOException e) { LOG.warn("Unable to response to ACK", e); @@ -1577,14 +1682,14 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } // This is only for ACK response - private byte[] buildCommand(byte action) { + private byte[] buildCommand() { byte[] result = new byte[7]; System.arraycopy(WatchXPlusConstants.CMD_HEADER, 0, result, 0, 5); result[2] = (byte) (result.length - 6); result[3] = WatchXPlusConstants.REQUEST; result[4] = (byte) sequenceNumber++; - result[5] = action; + result[5] = (byte) 0; result[result.length - 1] = calculateChecksum(result); return result; @@ -1631,10 +1736,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { sample.setProvider(provider); provider.addGBActivitySample(sample); - } catch (GBException ex) { - LOG.info((ex.getMessage())); } catch (Exception ex) { - LOG.info(ex.getMessage()); + LOG.info((ex.getMessage())); } } } @@ -1687,7 +1790,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { private WatchXPlusActivitySample createSample(DBHandler dbHandler, int timestamp) { Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); - WatchXPlusActivitySample sample = new WatchXPlusActivitySample( + + return new WatchXPlusActivitySample( timestamp, // ts deviceId, userId, // User id null, // Raw Data @@ -1698,8 +1802,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { ActivitySample.NOT_MEASURED, // Distance ActivitySample.NOT_MEASURED // Calories ); - - return sample; } private byte[] buildCommand(byte[] command, byte action) { @@ -1740,7 +1842,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } /** handle watch response for battery level - * @param value + * @param value - returned value */ private void handleBatteryState(byte[] value) { batteryInfo.state = value[8] == 1 ? BatteryState.BATTERY_NORMAL : BatteryState.BATTERY_LOW; @@ -1779,8 +1881,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { if (1 != value[8]) { z = false; } - LOG.info(" disconnectReminder: " + Boolean.valueOf(z) + " val: " + value[8]); - return; + LOG.info(" disconnectReminder: " + z + " val: " + value[8]); } // read preferences @@ -1797,7 +1898,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { activityUser.getAge(),activityUser.getGender()); } - private Handler mFindPhoneHandler = new Handler(); + private final Handler mFindPhoneHandler = new Handler(); private void onReverseFindDevice(boolean start) { if (start) { @@ -1832,8 +1933,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } } // Set Lift Wrist to Light Screen based on saved preferences - private WatchXPlusDeviceSupport setHeadsUpScreen(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { - return this.setHeadsUpScreen(transactionBuilder, + private void setHeadsUpScreen(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { + this.setHeadsUpScreen(transactionBuilder, WatchXPlusDeviceCoordinator.shouldEnableHeadsUpScreen(sharedPreferences)); } @@ -1860,8 +1961,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } - private WatchXPlusDeviceSupport setDisconnectReminder(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { - return this.setDisconnectReminder(transactionBuilder, + private void setDisconnectReminder(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { + this.setDisconnectReminder(transactionBuilder, WatchXPlusDeviceCoordinator.shouldEnableDisconnectReminder(sharedPreferences)); } @@ -1874,22 +1975,20 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { } // Request status of Disconnect reminder - public WatchXPlusDeviceSupport getDisconnectReminderStatus(TransactionBuilder transactionBuilder) { + private void getDisconnectReminderStatus(TransactionBuilder transactionBuilder) { transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_DISCONNECT_REMIND, WatchXPlusConstants.READ_VALUE)); - return this; } // Request status of Lift Wrist to Light Screen, and Shake to Ignore/Reject Call - public WatchXPlusDeviceSupport getShakeStatus(TransactionBuilder transactionBuilder) { + private void getShakeStatus(TransactionBuilder transactionBuilder) { transactionBuilder.write(getCharacteristic(WatchXPlusConstants.UUID_CHARACTERISTIC_WRITE), buildCommand(WatchXPlusConstants.CMD_SHAKE_SWITCH, WatchXPlusConstants.READ_VALUE)); - return this; } // calibrate altitude - private WatchXPlusDeviceSupport setAltitude(TransactionBuilder transactionBuilder) { + private void setAltitude(TransactionBuilder transactionBuilder) { int mAltitude = WatchXPlusDeviceCoordinator.getAltitude(getDevice().getAddress()); if (mAltitude < 0) { mAltitude = (Math.abs(mAltitude) ^ 65535) + 1; @@ -1905,7 +2004,6 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { WatchXPlusConstants.WRITE_VALUE, bArr)); LOG.info(" setAltitude: " + mAltitude); - return this; } // set time format @@ -1920,8 +2018,8 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { return this; } - private WatchXPlusDeviceSupport setLanguageAndTimeFormat(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { - return this.setLanguageAndTimeFormat(transactionBuilder, + private void setLanguageAndTimeFormat(TransactionBuilder transactionBuilder, SharedPreferences sharedPreferences) { + this.setLanguageAndTimeFormat(transactionBuilder, WatchXPlusDeviceCoordinator.getTimeMode(sharedPreferences), WatchXPlusDeviceCoordinator.getLanguage(sharedPreferences)); } @@ -1940,7 +2038,7 @@ public class WatchXPlusDeviceSupport extends AbstractBTLEDeviceSupport { case 2: return 1.0d * Math.pow(10.0d, -3.0d) * ((double) i2); case 3: - return (double) (1 * i2); + return (double) (i2); case 4: return 10.0d * Math.pow(10.0d, -6.0d) * ((double) i2); case 5: diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xwatch/XWatchSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xwatch/XWatchSupport.java index c3bc3a04c..4800cd200 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xwatch/XWatchSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/xwatch/XWatchSupport.java @@ -65,7 +65,6 @@ public class XWatchSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(XWatchSupport.class); private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); TransactionBuilder builder = null; - private DeviceInfo mDeviceInfo; private byte dayToFetch; //0 = Today; 1 = Yesterday ... private byte maxDayToFetch; long lastButtonTimestamp; @@ -359,7 +358,7 @@ public class XWatchSupport extends AbstractBTLEDeviceSupport { private void handleDeviceInfo(byte[] value, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { - mDeviceInfo = new DeviceInfo(value); + DeviceInfo mDeviceInfo = new DeviceInfo(value); LOG.warn("Device info: " + mDeviceInfo); versionCmd.hwVersion = "1.0"; versionCmd.fwVersion = "1.0"; From 7d834869a2c87caa6b44bd69e1e8ec3cef452673 Mon Sep 17 00:00:00 2001 From: mamutcho Date: Thu, 12 Dec 2019 19:34:44 +0200 Subject: [PATCH 031/546] merge with Gadgetbridge --- .../drawable-hdpi/ic_device_watchxplus.png | Bin 0 -> 1817 bytes .../ic_device_watchxplus_disabled.png | Bin 0 -> 1324 bytes .../activity_watchxplus_calibration.xml | 124 ++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 app/src/main/res/drawable-hdpi/ic_device_watchxplus.png create mode 100644 app/src/main/res/drawable-hdpi/ic_device_watchxplus_disabled.png create mode 100644 app/src/main/res/layout/activity_watchxplus_calibration.xml diff --git a/app/src/main/res/drawable-hdpi/ic_device_watchxplus.png b/app/src/main/res/drawable-hdpi/ic_device_watchxplus.png new file mode 100644 index 0000000000000000000000000000000000000000..749155a1928367971987a36462382e570d285638 GIT binary patch literal 1817 zcmV+!2j=*RP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf00v@9M??Ss00000 z`9r&Z00007bV*G`2jU3_3I{C!mIZ|X00xOkL_t(|UhSMcjNC*N$Dcpe_sPczK>;fTItmITpaGhsK!_>Lu;fxGiofPVjEC|VC?j;JEJZ7L{DFX8J{QXk4YUgM;rGY% z)%Bc?GA-MxD$<5#!cG#8soH%3;i512!*;WU*Uj`sG$hN5y9L1rRP^+plaR;%dQh6(Yeobe~zU9Xk<(k+>Xx zuXb!8uq~Sgl%)k_Ss?^M(qo9hIS}26ik#(L+hEh3JWc87D5X4uC!rf7okU^uAn8T; zf)DqslI&TQ(Q%qn#twmICcHz#AdnPEHZeZ}&7Z=!z{H%zDd9aL2F+_^jlVS^H0@aQ=~SA%P~Ll<01IYKwRi2q%tY5(jr6i16&AA)Px2iEo;7;J&Jh zh5YrqmWoR#YM2OghBtwf_vq+`!l<14M+oB89d(AyV3SCSn#RuXCXlUZrQEHOb1v0} zQ&Ja6coEm-%Za=RR2N4XcJrT-etTzs!Mob9>1nzMF7YOijuHoRz=$abyefutMfZ@p z7b$!+GcN+!4Bv_5vWrOAj-kbkTkVr}stzB`%!@#}+v`_bzrsx-;XAftKk}K->(%S1 z*R?EzG%-cS&Y-6!`4+E>w4VfX;$XkZiy46?R2^8lWKe-7e6)JfJ?Y}}@k|_S*Yic3 zj$0-cT#Ezvu;ZhPq{K0CPk;P95Y7c^yUI2o%H?aRRUB}z98I+yT1*EkobXB9Ln@GS%n9dw@5AT*m?%Dlq%-9xzxF!&SEp1L_lI zHc;MIOtaxtp!#hW9YGjb7pgvCR!5bo_mrDDm9Fsq;1Hwn>H7~>-%j}ChvRB({^6iP z7tu6?a3xOLgG28E0h>rb7phLv*a|TYO|#AW+E@vJfa}-+fpFr>#^%u%IHWJAwWk2gE*KazLCC;|Z(tc=!$&=b(}jlp((;wak|+2qY%f`6J{4wnCdkzzW1sND8IOUR3Mto&?H_vM3iB1a(dz6l)k+5gm{q-)Wrctf$ChjcAIYeo%7n1 z@;npocZy19C0nZ)5jL2wE&GO;1Vfw&qnOSg#Ps@whpIewS_P7O<-epzk8?`#ekaE! z%sQfI2Yp8eUV+Oyz0og>S7zBhMoO8CUwyzC* z!a}6VW4ed~Itv6;&QlawIix7^f0K0KR07?PG>7o}9-{vN0vIcscdOpR00000NkvXX Hu0mjfn@diW literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_device_watchxplus_disabled.png b/app/src/main/res/drawable-hdpi/ic_device_watchxplus_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..212cb44d55d0c32271ab171d78bffa455032ff6b GIT binary patch literal 1324 zcmV+{1=IS8P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf00v@9M??Ss00000 z`9r&Z00007bV*G`2jU3_3I{C!mIZ|X00f*#L_t(|UhSM;a+@#^hh?xyrhRE&^3pr_ z8aYLdkyGRtIYq9K$Ii3|$O#f-X!nPtWDP6+N&;hl9R>`7ct2K)V2U^Y2zA9tV&+#I zTm^M-71Y6?8g$xpW9H|=*LUA+`hWLcf^?HjH7{whhwriLBCtYq6!eciT1Q>724_%F zl&UOCS~e@s1c-o(G)>I1KH**jy}rJhrFF62@6D2%PC=0K{QPV_t&4|;hf0!FAZ3#% z2&>ST%elL|tLAIr?d`3aTS;Y!n46Lum-*91Ym)X=g&6&S{x4qwC!(ph9yTmT=3qsl2E z(9NpsUa4xjvPh0A#&9q7f`5e@8Jl7&_IteFNP}C<`@V#GbjZ2 z_xIH_mZ|%XA*vt@$odTLT}<774ABJnJo7QLD4HPGnYZ1>HA%$GqNrVr!v)x`Q=dSa zs2I#52m#kcNbojuNt#7an-mvg_VGdEaYuD$OQXvCkgGp`f2)_57qewNHe)0nA0N$9 zlc%RAv(%(*+Ys>Ge*XBSc3Gyrec!2@Y^%20RHdmN7X=sOlEcMFPYmqy8X(EQvnpRF zuT4s1m3BDce>ymKX7Q3MQy7#|DPl zg8Jhtu+Ui)gIUlJFVp zu^sIs_;L9$e9MZ`#au{|`pA*6$H3etvkC%rNn_+#e9Hl|3j$oySUI6sgdhMZxP4fX z9IzNcVCuaUvd=e*5;O%|)sTI@$4Eg0@h=(TtO9;q-%>cM$)Hd~5#&3ILS>~yFhL>H zo)cPDv@V7~rrri%|CreWf(oh!V@JzFMcIAoBV`;v^tdVbGilt7OWlqC&sphjaEa0_ zgfBVxJF`Bt2QEo~i>lLhZ{@4TbqR`6gWAqCB)Fy}5Uoga#uVDv#o028AY>E*wv!9q zCUh}&5v2wJE`ryOu1Bg|jO!RvSVRqK0|MHnxrru-pZ%%$JXROc1o_N{n3)x=1_4`d z#s)Zbe~CraAOtogxT*V(!6~TgO}sUsLp|w8p9q4`T5h~n@D}bSkrSdJ$i7sTICfYg z+cZ%r#hWCFiTf)-oLwxToi2slj>J&Ba04~Gya*XYCXLZc;pR3|S(fUn7iAGgNE7{D zyv?AlTY)ujd$UzLl9SM-U6&ZMfeXrq@PG$|+zk0b`~cR~(61CR#E$2Zc!(@^k^YptI^{PRj5<)FM=VtxcKwr3$WHP3Y9MD@3uq_AdvN*Jq?w`f6 i3=(vSw3qOUN2&kh)|vY=i-e&70000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +