diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5f555c22..b1c64ba55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
* Amazfit GTR 4: Whitelist fw 3.18.1.1 diff from 3.17.0.2
* Amazfit GTS 2 Mini: Add missing alexa menu item
* Bangle.js: Fix updating timezone in settings.json if the timezone is zero
+* Fossil/Skagen Hybrids: Pair watch to phone, fixes repeating confirmation request
* Huami: Implement repeated activity fetching
* Sony WH-1000XM4: Add speak-to-chat
* Sony Headphones: Add button modes help
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java
index 53369c669..402f05272 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java
@@ -122,7 +122,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application.ApplicationsListRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.async.ConfirmAppStatusRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.CheckDeviceNeedsConfirmationRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.CheckDevicePairingRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.ConfirmOnDeviceRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.PerformDevicePairingRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfiguration;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfigurationPutRequest;
@@ -269,7 +271,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
}
boolean shouldAuthenticateOnWatch = getDeviceSpecificPreferences().getBoolean("enable_on_device_confirmation", true);
if (!shouldAuthenticateOnWatch) {
- GB.toast("Skipping on-device confirmation", Toast.LENGTH_SHORT, GB.INFO);
+ GB.toast(getContext().getString(R.string.fossil_hr_confirmation_skipped), Toast.LENGTH_SHORT, GB.INFO);
initializeAfterWatchConfirmation(false);
return;
}
@@ -282,7 +284,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
if (!(fossilRequest instanceof ConfirmOnDeviceRequest)) {
return;
}
- GB.toast("Confirmation timeout, continuing", Toast.LENGTH_SHORT, GB.INFO);
+ GB.toast(getContext().getString(R.string.fossil_hr_confirmation_timeout), Toast.LENGTH_SHORT, GB.INFO);
((ConfirmOnDeviceRequest) fossilRequest).onResult(false);
}
};
@@ -294,14 +296,16 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
GB.log("needs confirmation: " + needsConfirmation, GB.INFO, null);
if (needsConfirmation) {
final Timer timer = new Timer();
- GB.toast("please confirm on device.", Toast.LENGTH_SHORT, GB.INFO);
+ GB.toast(getContext().getString(R.string.fossil_hr_confirm_connection), Toast.LENGTH_SHORT, GB.INFO);
queueWrite(new ConfirmOnDeviceRequest() {
@Override
public void onResult(boolean confirmationSuccess) {
isFinished = true;
timer.cancel();
- if (!confirmationSuccess) {
- GB.toast("connection unconfirmed on watch, unauthenticated mode", Toast.LENGTH_LONG, GB.ERROR);
+ if (confirmationSuccess) {
+ pairToWatch();
+ } else {
+ GB.toast(getContext().getString(R.string.fossil_hr_connection_not_confirmed), Toast.LENGTH_LONG, GB.ERROR);
}
initializeAfterWatchConfirmation(confirmationSuccess);
}
@@ -314,6 +318,29 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
});
}
+ private void pairToWatch() {
+ queueWrite(new CheckDevicePairingRequest() {
+ @Override
+ public void onResult(boolean pairingStatus) {
+ GB.log("watch pairing status: " + pairingStatus, GB.INFO, null);
+ if (!pairingStatus) {
+ queueWrite(new PerformDevicePairingRequest() {
+ @Override
+ public void onResult(boolean pairingSuccess) {
+ isFinished = true;
+ GB.log("watch pairing result: " + pairingSuccess, GB.INFO, null);
+ if (pairingSuccess) {
+ GB.toast(getContext().getString(R.string.fossil_hr_pairing_successful), Toast.LENGTH_LONG, GB.ERROR);
+ } else {
+ GB.toast(getContext().getString(R.string.fossil_hr_pairing_failed), Toast.LENGTH_LONG, GB.ERROR);
+ }
+ }
+ }, true);
+ }
+ }
+ });
+ }
+
private void respondToAlexa(String message, boolean isResponse){
queueWrite(new AlexaMessageSetRequest(message, isResponse, this));
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDevicePairingRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDevicePairingRequest.java
new file mode 100644
index 000000000..207953e9d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/CheckDevicePairingRequest.java
@@ -0,0 +1,44 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
+
+public class CheckDevicePairingRequest extends FossilRequest {
+ protected boolean isFinished = false;
+
+ @Override
+ public byte[] getStartSequence() {
+ return new byte[]{0x01, 0x16};
+ }
+
+ @Override
+ public void handleResponse(BluetoothGattCharacteristic characteristic) {
+ if(!characteristic.getUuid().equals(getRequestUUID())){
+ throw new RuntimeException("wrong characteristic responded to pairing");
+ }
+ byte[] value = characteristic.getValue();
+ if(value.length != 3){
+ throw new RuntimeException("wrong pairing response length");
+ }
+ if(value[0] != 0x03 || value[1] != 0x16){
+ throw new RuntimeException("wrong pairing response bytes");
+ }
+ this.onResult(value[2] == 0x01);
+ this.isFinished = true;
+ }
+
+ public void onResult(boolean confirmationSuccess){};
+
+ @Override
+ public boolean isFinished() {
+ return isFinished;
+ }
+
+ @Override
+ public UUID getRequestUUID() {
+ return UUID.fromString("3dda0002-957f-7d4a-34a6-74696673696d");
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/PerformDevicePairingRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/PerformDevicePairingRequest.java
new file mode 100644
index 000000000..5490c2385
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/authentication/PerformDevicePairingRequest.java
@@ -0,0 +1,8 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication;
+
+public class PerformDevicePairingRequest extends CheckDevicePairingRequest {
+ @Override
+ public byte[] getStartSequence() {
+ return new byte[]{0x02, 0x16};
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ef8801043..ef1103b6a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2083,4 +2083,10 @@
Root Directory
Address
Username
+ Please confirm on your watch
+ Connection not confirmed on watch, using unauthenticated mode
+ Pairing with watch successful
+ Pairing with watch failed
+ Skipping on-device confirmation
+ Confirmation timeout, continuing