mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-06-25 06:21:04 +02:00
Fossil Gen6. Hybrid: added basic support for Hybrid Gen 6 (#2775)
This PR aims to add support for the newer Fossil Gen. 6 Hybrid models, which are pretty similar to the older HR's. Here's my checklist - [x] make GB recognize and accept new watches - [ ] find out how SPO2 is transmitted - [ ] extend activity data to include Oxygen data - [x] create timeout for requests to avoid deadlocks - [x] fix device vibration on every reconnect - [ ] create API for voice commands - [x] figure out how the voice data works Co-authored-by: Daniel Dakhno <dakhnod@gmail.com> Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2775 Co-authored-by: dakhnod <dakhnod@noreply.codeberg.org> Co-committed-by: dakhnod <dakhnod@noreply.codeberg.org>
This commit is contained in:
parent
0f052f5467
commit
fe485d80ec
|
@ -290,13 +290,18 @@ public class QHybridCoordinator extends AbstractBLEDeviceCoordinator {
|
||||||
private boolean isHybridHR() {
|
private boolean isHybridHR() {
|
||||||
List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
|
List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
|
||||||
for(GBDevice device : devices){
|
for(GBDevice device : devices){
|
||||||
if(isFossilHybrid(device) && device.getName().startsWith("Hybrid HR")){
|
if(isHybridHR(device)){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isHybridHR(GBDevice device){
|
||||||
|
if(!isFossilHybrid(device)) return false;
|
||||||
|
return device.getName().startsWith("Hybrid HR") || device.getName().equals("Fossil Gen. 6 Hybrid");
|
||||||
|
}
|
||||||
|
|
||||||
private Version getFirmwareVersion() {
|
private Version getFirmwareVersion() {
|
||||||
List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
|
List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
|
||||||
for (GBDevice device : devices) {
|
for (GBDevice device : devices) {
|
||||||
|
|
|
@ -143,6 +143,7 @@ public class QHybridSupport extends QHybridBaseSupport {
|
||||||
|
|
||||||
public QHybridSupport() {
|
public QHybridSupport() {
|
||||||
super(logger);
|
super(logger);
|
||||||
|
addSupportedService(UUID.fromString("108b5094-4c03-e51c-555e-105d1a1155f0"));
|
||||||
addSupportedService(UUID.fromString("3dda0001-957f-7d4a-34a6-74696673696d"));
|
addSupportedService(UUID.fromString("3dda0001-957f-7d4a-34a6-74696673696d"));
|
||||||
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
|
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
|
||||||
addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
|
addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
|
||||||
|
@ -480,6 +481,10 @@ public class QHybridSupport extends QHybridBaseSupport {
|
||||||
for (int i = 2; i <= 7; i++)
|
for (int i = 2; i <= 7; i++)
|
||||||
builder.notify(getCharacteristic(UUID.fromString("3dda000" + i + "-957f-7d4a-34a6-74696673696d")), true);
|
builder.notify(getCharacteristic(UUID.fromString("3dda000" + i + "-957f-7d4a-34a6-74696673696d")), true);
|
||||||
|
|
||||||
|
builder.notify(getCharacteristic(UUID.fromString("010541ae-efe8-11c0-91c0-105d1a1155f0")), true);
|
||||||
|
builder.notify(getCharacteristic(UUID.fromString("fef9589f-9c21-4d19-9fc0-105d1a1155f0")), true);
|
||||||
|
builder.notify(getCharacteristic(UUID.fromString("842d2791-0d20-4ce4-1ada-105d1a1155f0")), true);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.read(getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")))
|
.read(getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")))
|
||||||
.read(getCharacteristic(UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb")))
|
.read(getCharacteristic(UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb")))
|
||||||
|
|
|
@ -83,7 +83,9 @@ public abstract class WatchAdapter {
|
||||||
case "IV.0.0":
|
case "IV.0.0":
|
||||||
return "Hybrid HR";
|
return "Hybrid HR";
|
||||||
case "DN.1.0":
|
case "DN.1.0":
|
||||||
return "Hybrid HR";
|
return "Hybrid HR Collider";
|
||||||
|
case "VA.0.0":
|
||||||
|
return "Fossil Gen. 6 Hybrid";
|
||||||
}
|
}
|
||||||
return "unknown Q";
|
return "unknown Q";
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ public final class WatchAdapterFactory {
|
||||||
char hardwareVersion = firmwareVersion.charAt(2);
|
char hardwareVersion = firmwareVersion.charAt(2);
|
||||||
if(hardwareVersion == '1') return new FossilHRWatchAdapter(deviceSupport);
|
if(hardwareVersion == '1') return new FossilHRWatchAdapter(deviceSupport);
|
||||||
if(firmwareVersion.startsWith("IV0")) return new FossilHRWatchAdapter(deviceSupport);
|
if(firmwareVersion.startsWith("IV0")) return new FossilHRWatchAdapter(deviceSupport);
|
||||||
|
if(firmwareVersion.startsWith("VA")) return new FossilHRWatchAdapter(deviceSupport);
|
||||||
|
|
||||||
char major = firmwareVersion.charAt(6);
|
char major = firmwareVersion.charAt(6);
|
||||||
switch (major){
|
switch (major){
|
||||||
|
|
|
@ -22,6 +22,8 @@ import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
@ -114,9 +116,37 @@ public class FossilWatchAdapter extends WatchAdapter {
|
||||||
super(deviceSupport);
|
super(deviceSupport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final int REQUEST_TIMEOUT = 60 * 1000;
|
||||||
|
|
||||||
|
private Looper timeoutLooper = null;
|
||||||
|
private Handler timeoutHandler;
|
||||||
|
protected Thread timeoutThread = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Looper.prepare();
|
||||||
|
timeoutLooper = Looper.myLooper();
|
||||||
|
timeoutHandler = new Handler(timeoutLooper);
|
||||||
|
Looper.loop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private Runnable requestTimeoutRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String requestName = "unknown";
|
||||||
|
if(fossilRequest != null){
|
||||||
|
requestName = fossilRequest.getName();
|
||||||
|
}
|
||||||
|
log(String.format("Request %s timed out, queing next request", requestName));
|
||||||
|
fossilRequest = null;
|
||||||
|
queueNextRequest();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
timeoutThread.start();
|
||||||
|
|
||||||
playPairingAnimation();
|
playPairingAnimation();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
queueWrite(new RequestMtuRequest(512), false);
|
queueWrite(new RequestMtuRequest(512), false);
|
||||||
|
@ -125,6 +155,26 @@ public class FossilWatchAdapter extends WatchAdapter {
|
||||||
getDeviceInfos();
|
getDeviceInfos();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void restartRequestTimeout(){
|
||||||
|
if(timeoutLooper == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stopRequestTimeout();
|
||||||
|
log("restarting request timeout");
|
||||||
|
timeoutHandler.postDelayed(
|
||||||
|
requestTimeoutRunnable,
|
||||||
|
REQUEST_TIMEOUT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopRequestTimeout(){
|
||||||
|
if(timeoutLooper == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timeoutHandler.removeCallbacks(requestTimeoutRunnable);
|
||||||
|
log("stopped request timeout");
|
||||||
|
}
|
||||||
|
|
||||||
public short getSupportedFileVersion(FileHandle handle) {
|
public short getSupportedFileVersion(FileHandle handle) {
|
||||||
return this.supportedFileVersions.getSupportedFileVersion(handle);
|
return this.supportedFileVersions.getSupportedFileVersion(handle);
|
||||||
}
|
}
|
||||||
|
@ -461,6 +511,8 @@ public class FossilWatchAdapter extends WatchAdapter {
|
||||||
return true;
|
return true;
|
||||||
case "DN.1.0":
|
case "DN.1.0":
|
||||||
return true;
|
return true;
|
||||||
|
case "VA.0.0":
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
throw new UnsupportedOperationException("model " + modelNumber + " not supported");
|
throw new UnsupportedOperationException("model " + modelNumber + " not supported");
|
||||||
}
|
}
|
||||||
|
@ -477,6 +529,8 @@ public class FossilWatchAdapter extends WatchAdapter {
|
||||||
return false;
|
return false;
|
||||||
case "DN.1.0":
|
case "DN.1.0":
|
||||||
return false;
|
return false;
|
||||||
|
case "VA.0.0":
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
throw new UnsupportedOperationException("Model " + modelNumber + " not supported");
|
throw new UnsupportedOperationException("Model " + modelNumber + " not supported");
|
||||||
}
|
}
|
||||||
|
@ -613,6 +667,7 @@ public class FossilWatchAdapter extends WatchAdapter {
|
||||||
if (requestFinished) {
|
if (requestFinished) {
|
||||||
log(fossilRequest.getName() + " finished");
|
log(fossilRequest.getName() + " finished");
|
||||||
fossilRequest = null;
|
fossilRequest = null;
|
||||||
|
stopRequestTimeout();
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -795,11 +850,13 @@ public class FossilWatchAdapter extends WatchAdapter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log("executing request: " + request.getName());
|
log("executing request: " + request.getName());
|
||||||
|
restartRequestTimeout();
|
||||||
this.fossilRequest = request;
|
this.fossilRequest = request;
|
||||||
new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
|
new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
|
||||||
|
|
||||||
if (request.isFinished()) {
|
if (request.isFinished()) {
|
||||||
this.fossilRequest = null;
|
this.fossilRequest = null;
|
||||||
|
stopRequestTimeout();
|
||||||
queueNextRequest();
|
queueNextRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -810,6 +867,7 @@ public class FossilWatchAdapter extends WatchAdapter {
|
||||||
log("dropping requetst " + request.getName());
|
log("dropping requetst " + request.getName());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
restartRequestTimeout();
|
||||||
new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
|
new TransactionBuilder(request.getClass().getSimpleName()).write(getDeviceSupport().getCharacteristic(request.getRequestUUID()), request.getRequestData()).queue(getDeviceSupport().getQueue());
|
||||||
|
|
||||||
queueNextRequest();
|
queueNextRequest();
|
||||||
|
|
|
@ -24,9 +24,13 @@ import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.reque
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil.convertDrawableToBitmap;
|
import static nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil.convertDrawableToBitmap;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.util.StringUtils.shortenPackageName;
|
import static nodomain.freeyourgadget.gadgetbridge.util.StringUtils.shortenPackageName;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.bluetooth.BluetoothGatt;
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
@ -37,6 +41,11 @@ import android.graphics.Paint;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.os.RemoteException;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
@ -111,6 +120,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.DismissTextNotificationRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.DismissTextNotificationRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayCallNotificationRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayCallNotificationRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayTextNotificationRequest;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayTextNotificationRequest;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.alexa.AlexaMessageSetRequest;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application.ApplicationInformation;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application.ApplicationInformation;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application.ApplicationsListRequest;
|
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.async.ConfirmAppStatusRequest;
|
||||||
|
@ -153,6 +163,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||||
|
|
||||||
public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||||
|
public static final int MESSAGE_WHAT_VOICE_DATA_RECEIVED = 0;
|
||||||
|
|
||||||
private byte[] phoneRandomNumber;
|
private byte[] phoneRandomNumber;
|
||||||
private byte[] watchRandomNumber;
|
private byte[] watchRandomNumber;
|
||||||
|
|
||||||
|
@ -178,6 +190,22 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||||
|
|
||||||
List<ApplicationInformation> installedApplications = new ArrayList();
|
List<ApplicationInformation> installedApplications = new ArrayList();
|
||||||
|
|
||||||
|
Messenger voiceMessenger = null;
|
||||||
|
|
||||||
|
ServiceConnection voiceServiceConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
GB.log("attached to voice service", GB.INFO, null);
|
||||||
|
voiceMessenger = new Messenger(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
GB.log("detached from voice service", GB.INFO, null);
|
||||||
|
voiceMessenger = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
enum CONNECTION_MODE {
|
enum CONNECTION_MODE {
|
||||||
NOT_INITIALIZED,
|
NOT_INITIALIZED,
|
||||||
AUTHENTICATED,
|
AUTHENTICATED,
|
||||||
|
@ -188,6 +216,8 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
timeoutThread.start();
|
||||||
|
|
||||||
saveRawActivityFiles = getDeviceSpecificPreferences().getBoolean("save_raw_activity_files", false);
|
saveRawActivityFiles = getDeviceSpecificPreferences().getBoolean("save_raw_activity_files", false);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
@ -287,12 +317,124 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void respondToAlexa(String message, boolean isResponse){
|
||||||
|
queueWrite(new AlexaMessageSetRequest(message, isResponse, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachToVoiceService(){
|
||||||
|
String servicePackage = getDeviceSpecificPreferences().getString("voice_service_package", "");
|
||||||
|
String servicePath = getDeviceSpecificPreferences().getString("voice_service_class", "");
|
||||||
|
|
||||||
|
if(servicePackage.isEmpty()){
|
||||||
|
GB.toast("voice service package is not configured", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
respondToAlexa("voice service package not configured on phone", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(servicePath.isEmpty()){
|
||||||
|
respondToAlexa("voice service class not configured on phone", true);
|
||||||
|
GB.toast("voice service class is not configured", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentName component = new ComponentName(servicePackage, servicePath);
|
||||||
|
|
||||||
|
// extract to somewhere
|
||||||
|
Intent voiceIntent = new Intent("nodomain.freeyourgadget.gadgetbridge.VOICE_COMMAND");
|
||||||
|
voiceIntent.setComponent(component);
|
||||||
|
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
|
flags |= Service.BIND_AUTO_CREATE;
|
||||||
|
|
||||||
|
GB.log("binding to voice service...", GB.INFO, null);
|
||||||
|
|
||||||
|
getContext().bindService(
|
||||||
|
voiceIntent,
|
||||||
|
voiceServiceConnection,
|
||||||
|
flags
|
||||||
|
);
|
||||||
|
|
||||||
|
PackageManager pm = getContext().getPackageManager();
|
||||||
|
boolean serviceEnabled = true;
|
||||||
|
try {
|
||||||
|
int enabledState = pm.getComponentEnabledSetting(component);
|
||||||
|
|
||||||
|
if(enabledState != PackageManager.COMPONENT_ENABLED_STATE_ENABLED && enabledState != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT){
|
||||||
|
respondToAlexa("voice service is disabled on phone", true);
|
||||||
|
GB.toast("voice service is disabled", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
serviceEnabled = false;
|
||||||
|
}
|
||||||
|
}catch (IllegalArgumentException e){
|
||||||
|
serviceEnabled = false;
|
||||||
|
respondToAlexa("voice service not found on phone", true);
|
||||||
|
GB.toast("voice service not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!serviceEnabled){
|
||||||
|
detachFromVoiceService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void detachFromVoiceService(){
|
||||||
|
getContext().unbindService(voiceServiceConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleVoiceStatus(byte status){
|
||||||
|
if(status == 0x00){
|
||||||
|
attachToVoiceService();
|
||||||
|
}else if(status == 0x01){
|
||||||
|
detachFromVoiceService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleVoiceStatusCharacteristic(BluetoothGattCharacteristic characteristic){
|
||||||
|
byte[] value = characteristic.getValue();
|
||||||
|
handleVoiceStatus(value[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleVoiceDataCharacteristic(BluetoothGattCharacteristic characteristic){
|
||||||
|
if(voiceMessenger == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Message message = Message.obtain(
|
||||||
|
null,
|
||||||
|
MESSAGE_WHAT_VOICE_DATA_RECEIVED
|
||||||
|
);
|
||||||
|
Bundle dataBundle = new Bundle(1);
|
||||||
|
dataBundle.putByteArray("VOICE_DATA", characteristic.getValue());
|
||||||
|
dataBundle.putString("VOICE_ENCODING", "OPUS");
|
||||||
|
message.setData(dataBundle);
|
||||||
|
try {
|
||||||
|
voiceMessenger.send(message);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
GB.log("error sending voice data to service", GB.ERROR, e);
|
||||||
|
GB.toast("error sending voice data to service", Toast.LENGTH_LONG, GB.ERROR);
|
||||||
|
voiceMessenger = null;
|
||||||
|
detachFromVoiceService();
|
||||||
|
respondToAlexa("error sending voice data to service", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
||||||
|
switch (characteristic.getUuid().toString()){
|
||||||
|
case "010541ae-efe8-11c0-91c0-105d1a1155f0":
|
||||||
|
handleVoiceStatusCharacteristic(characteristic);
|
||||||
|
return true;
|
||||||
|
case "842d2791-0d20-4ce4-1ada-105d1a1155f0":
|
||||||
|
handleVoiceDataCharacteristic(characteristic);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onCharacteristicChanged(gatt, characteristic);
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeAfterWatchConfirmation(boolean authenticated) {
|
private void initializeAfterWatchConfirmation(boolean authenticated) {
|
||||||
setNotificationConfigurations();
|
setNotificationConfigurations();
|
||||||
setQuickRepliesConfiguration();
|
setQuickRepliesConfiguration();
|
||||||
|
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
setVibrationStrength();
|
// setVibrationStrengthFromConfig();
|
||||||
setUnitsConfig();
|
setUnitsConfig();
|
||||||
syncSettings();
|
syncSettings();
|
||||||
setTime();
|
setTime();
|
||||||
|
@ -330,7 +472,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||||
queueWrite(new SelectedThemePutRequest(this, appName));
|
queueWrite(new SelectedThemePutRequest(this, appName));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setVibrationStrength() {
|
private void setVibrationStrengthFromConfig() {
|
||||||
Prefs prefs = new Prefs(getDeviceSpecificPreferences());
|
Prefs prefs = new Prefs(getDeviceSpecificPreferences());
|
||||||
int vibrationStrengh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE, 2);
|
int vibrationStrengh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE, 2);
|
||||||
if (vibrationStrengh > 0) {
|
if (vibrationStrengh > 0) {
|
||||||
|
@ -1302,13 +1444,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTestNewFunction() {
|
public void onTestNewFunction() {
|
||||||
queueWrite((FileEncryptedInterface) new ConfigurationGetRequest(this){
|
setVibrationStrengthFromConfig();
|
||||||
@Override
|
|
||||||
protected void handleConfiguration(ConfigItem[] items) {
|
|
||||||
super.handleConfiguration(items);
|
|
||||||
LOG.debug(items[items.length - 1].toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getSecretKey() throws IllegalAccessException {
|
public byte[] getSecretKey() throws IllegalAccessException {
|
||||||
|
@ -1447,7 +1583,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||||
overwriteButtons(null);
|
overwriteButtons(null);
|
||||||
break;
|
break;
|
||||||
case DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE:
|
case DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE:
|
||||||
setVibrationStrength();
|
setVibrationStrengthFromConfig();
|
||||||
break;
|
break;
|
||||||
case "force_white_color_scheme":
|
case "force_white_color_scheme":
|
||||||
loadBackground();
|
loadBackground();
|
||||||
|
@ -1611,6 +1747,8 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||||
|
|
||||||
boolean versionSupportsConfirmation = getCleanFWVersion().compareTo(new Version("2.22")) != -1;
|
boolean versionSupportsConfirmation = getCleanFWVersion().compareTo(new Version("2.22")) != -1;
|
||||||
|
|
||||||
|
versionSupportsConfirmation |= getDeviceSupport().getDevice().getModel().startsWith("VA");
|
||||||
|
|
||||||
if(!versionSupportsConfirmation){
|
if(!versionSupportsConfirmation){
|
||||||
GB.toast("not supported in this version", Toast.LENGTH_SHORT, GB.ERROR);
|
GB.toast("not supported in this version", Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.alexa;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
|
||||||
|
|
||||||
|
public class AlexaMessageSetRequest extends JsonPutRequest {
|
||||||
|
public AlexaMessageSetRequest(String message, boolean isResponse, FossilHRWatchAdapter adapter) {
|
||||||
|
super(createResponseObject(message, isResponse), adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONObject createResponseObject(String message, boolean isResponse){
|
||||||
|
try {
|
||||||
|
return new JSONObject()
|
||||||
|
.put("push", new JSONObject()
|
||||||
|
.put("set", new JSONObject()
|
||||||
|
.put("AlexaApp._.config.msg", new JSONObject()
|
||||||
|
.put("res", message)
|
||||||
|
.put("is_resp", isResponse)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -147,6 +147,18 @@
|
||||||
android:title="@string/qhybrid_title_on_device_confirmation"
|
android:title="@string/qhybrid_title_on_device_confirmation"
|
||||||
android:summary="@string/qhybrid_summary_on_device_confirmation" />
|
android:summary="@string/qhybrid_summary_on_device_confirmation" />
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="voice_service_package"
|
||||||
|
android:title="Voice service package"
|
||||||
|
android:summary="Application that contains the service handling voice commands"
|
||||||
|
app:useSimpleSummaryProvider="true"/>
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="voice_service_class"
|
||||||
|
android:title="Voice service full path"
|
||||||
|
android:summary="Full service path handling voice commands"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user