diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java index 5cd95dbe5c..bf4a3f3029 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiCoordinator.java @@ -20,6 +20,7 @@ import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; +import android.text.TextUtils; import java.nio.ByteBuffer; import java.util.List; @@ -368,6 +369,10 @@ public class HuaweiCoordinator { deviceSpecificSettings.addRootScreen(R.xml.devicesettings_musicmanagement); } + if(supportsSendCountryCode()) { + deviceSpecificSettings.addRootScreen(R.xml.devicesettings_huawei_country_code); + } + // Time if (supportsDateFormat()) { final List dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME); @@ -895,6 +900,18 @@ public class HuaweiCoordinator { return false; } + public boolean supportsSendCountryCode() { + if (supportsExpandCapability()) + return supportsExpandCapability(30); + return false; + } + + public boolean supportsSendSiteId() { + if (supportsExpandCapability()) + return supportsExpandCapability(170); + return false; + } + public boolean supportsPromptPushMessage () { // do not ask for capabilities under specific condition // if (deviceType == 10 && deviceVersion == 73617766697368 && deviceSoftVersion == 372E312E31) -> leo device @@ -1147,4 +1164,14 @@ public class HuaweiCoordinator { public HeartRateZonesSpec getHeartRateZonesSpec(@NonNull GBDevice device) { return new HuaweiHeartRateZonesSpec(device, this); } + + public String getCountryCode(GBDevice gbDevice) { + return getDeviceSpecificSharedPreferences(gbDevice).getString("pref_huawei_country_code", ""); + } + + public boolean getSendCountryCodeEnabled(GBDevice gbDevice) { + return getDeviceSpecificSharedPreferences(gbDevice).getBoolean("pref_huawei_country_code_enable", false) && !TextUtils.isEmpty(getCountryCode(gbDevice)); + } + + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java index a6b06dbc5e..96896eb5b0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiPacket.java @@ -648,6 +648,8 @@ public class HuaweiPacket { return new AccountRelated.SendAccountToDevice.Response(paramsProvider).fromPacket(this); case AccountRelated.SendExtendedAccountToDevice.id: return new AccountRelated.SendExtendedAccountToDevice.Response(paramsProvider).fromPacket(this); + case AccountRelated.SendCountryCodeToDevice.id: + return new AccountRelated.SendCountryCodeToDevice.Response(paramsProvider).fromPacket(this); default: this.isEncrypted = this.attemptDecrypt(); // Helps with debugging return this; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSettingsCustomizer.java index 99a04f0180..65588585b5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSettingsCustomizer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiSettingsCustomizer.java @@ -25,6 +25,7 @@ import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.SwitchPreferenceCompat; +import java.util.Map; import java.util.Set; import java.util.Collections; @@ -76,10 +77,14 @@ public class HuaweiSettingsCustomizer implements DeviceSpecificSettingsCustomize SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()); boolean statusLiftWrist = sharedPrefs.getBoolean(PREF_LIFTWRIST_NOSHED, false); - dndStart.setEnabled(dndState.equals("scheduled")); - dndEnd.setEnabled(dndState.equals("scheduled")); - dndLifWrist.setEnabled(statusLiftWrist && !dndState.equals("off")); - dndNotWear.setEnabled(dndState.equals("off")); + if (dndStart != null) + dndStart.setEnabled(dndState.equals("scheduled")); + if (dndEnd != null) + dndEnd.setEnabled(dndState.equals("scheduled")); + if (dndLifWrist != null) + dndLifWrist.setEnabled(statusLiftWrist && !dndState.equals("off")); + if (dndNotWear != null) + dndNotWear.setEnabled(dndState.equals("off")); } if (preference.getKey().equals("huawei_reparse_workout_data")) { if (((SwitchPreferenceCompat) preference).isChecked()) { @@ -141,17 +146,21 @@ public class HuaweiSettingsCustomizer implements DeviceSpecificSettingsCustomize boolean supportsSpO2 = this.coordinator.supportsSPo2(); forceOptions.setVisible(!supportsSmartAlarm || !supportsWearLocation || !supportsHeartRate || !supportsSpO2); final SwitchPreferenceCompat forceSmartAlarm = handler.findPreference(PREF_FORCE_ENABLE_SMART_ALARM); - forceSmartAlarm.setVisible(!supportsSmartAlarm); + if(forceSmartAlarm != null) + forceSmartAlarm.setVisible(!supportsSmartAlarm); final SwitchPreferenceCompat forceWearLocation = handler.findPreference(PREF_FORCE_ENABLE_WEAR_LOCATION); - forceWearLocation.setVisible(!supportsWearLocation); + if(forceWearLocation != null) + forceWearLocation.setVisible(!supportsWearLocation); final SwitchPreferenceCompat forceHeartRate = handler.findPreference(PREF_FORCE_ENABLE_HEARTRATE_SUPPORT); - forceHeartRate.setVisible(!supportsHeartRate); + if(forceHeartRate != null) + forceHeartRate.setVisible(!supportsHeartRate); final SwitchPreferenceCompat forceSpO2 = handler.findPreference(PREF_FORCE_ENABLE_SPO2_SUPPORT); - forceSpO2.setVisible(!supportsSpO2); + if(forceSpO2 != null) + forceSpO2.setVisible(!supportsSpO2); } final SwitchPreferenceCompat sleepBreath = handler.findPreference(PREF_HUAWEI_SLEEP_BREATH); - if(sleepBreath != null && !coordinator.supportsSleepBreath()) { + if (sleepBreath != null && !coordinator.supportsSleepBreath()) { sleepBreath.setVisible(false); } @@ -188,6 +197,13 @@ public class HuaweiSettingsCustomizer implements DeviceSpecificSettingsCustomize if (calendarLookahead != null) { calendarLookahead.setVisible(false); } + + final ListPreference countryCodeList = handler.findPreference("pref_huawei_country_code"); + if (countryCodeList != null) { + Map countries = HuaweiUtil.getCountriesMap(); + countryCodeList.setEntries(countries.keySet().toArray(new String[0])); + countryCodeList.setEntryValues(countries.values().toArray(new String[0])); + } } @Override @@ -206,7 +222,7 @@ public class HuaweiSettingsCustomizer implements DeviceSpecificSettingsCustomize } - public static final Creator CREATOR= new Creator() { + public static final Creator CREATOR = new Creator<>() { @Override public HuaweiSettingsCustomizer createFromParcel(Parcel parcel) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiUtil.java index 7d2acfa961..6440c25267 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiUtil.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/HuaweiUtil.java @@ -16,13 +16,21 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.huawei; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +38,46 @@ import org.slf4j.LoggerFactory; public class HuaweiUtil { private static final Logger LOG = LoggerFactory.getLogger(HuaweiUtil.class); + + // NOTE: List can be incomplete + private static final Map regions = new HashMap<>(); + static { + // China + regions.put(1, new String[] {"CN"}); + // Asia, Africa, Latin America + regions.put(5, new String[] {"AE","AG","AI","AM","AR","AW","AZ","BD","BF","BH","BI","BJ","BN","BO","BR","BW","BY","BZ","CD","CF","CG","CI","CK","CL","CM","CO","CR","CV","DJ","DO","DZ","EC","EG","ER","ET","FJ","GA","GD","GE","GF","GH","GM","GN","GP","GQ","GT","GW","GY","HK","HN","ID","IN","IQ","JM","JO","JP","KE","KG","KH","KM","KR","KW","KY","KZ","LA","LB","LC","LK","LR","LS","MA","MG","ML","MM","MN","MO","MQ","MR","MS","MU","MV","MW","MX","MY","MZ","NA","NE","NG","NI","NP","NR","OM","PA","PE","PF","PG","PH","PK","PR","PS","PY","QA","RE","SA","SB","SC","SG","SL","SN","SO","SR","ST","SV","SZ","TD","TG","TH","TJ","TN","TO","TT","TW","TZ","UG","UY","UZ","VE","VG","VN","YT","ZA","ZM","ZW"}); + // Europe + regions.put(7, new String[] {"AD","AL","AT","AU","BA","BE","BG","CA","CH","CY","CZ","DE","DK","EE","ES","FI","FO","FR","GB","GL","GR","HR","HU","IE","IS","IT","LI","LT","LU","LV","MC","MD","ME","MK","MT","NL","NO","NZ","PL","PT","RO","RS","SE","SI","SK","SM","TR","UA","VA"}); + // ru + regions.put(8, new String[] {"RU"}); + } + + public static int getSiteIdByCountryCode(final String countryCode) { + if(TextUtils.isEmpty(countryCode)) { + return 0; + } + for(Map.Entry r: regions.entrySet()) { + if(Arrays.asList(r.getValue()).contains(countryCode)) { + return r.getKey(); + } + } + return 0; + } + + @NonNull + public static Map getCountriesMap() { + Map countries = new TreeMap<>(); + Locale[] locales = Locale.getAvailableLocales(); + for (Locale locale : locales) { + String country = locale.getDisplayCountry(); + String countryCode = locale.getCountry(); + if (!country.trim().isEmpty() && countryCode.matches("^[A-Z][A-Z]$") && !countries.containsKey(country)) { + countries.put(country + " (" + countryCode + ")", countryCode); + } + } + return countries; + } + public static byte[] timeToByte(String time) { Calendar calendar = Calendar.getInstance(); DateFormat df = new SimpleDateFormat("HH:mm", Locale.ENGLISH); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/AccountRelated.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/AccountRelated.java index ecfb88d9c3..c27e4c2500 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/AccountRelated.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huawei/packets/AccountRelated.java @@ -79,4 +79,31 @@ public class AccountRelated { } } } + + public static class SendCountryCodeToDevice { + public static final byte id = 0x0a; + + public static class Request extends HuaweiPacket { + public Request (ParamsProvider paramsProvider, String countryCode, Byte siteId) { + super(paramsProvider); + + this.serviceId = AccountRelated.id; + this.commandId = id; + + this.tlv = new HuaweiTLV(); + + tlv.put(0x01, countryCode); + if(siteId != null) { + tlv.put(0x02, siteId); + } + this.complete = true; + } + } + + public static class Response extends HuaweiPacket { + public Response (ParamsProvider paramsProvider) { + super(paramsProvider); + } + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java index 544de5b26e..666ecf06b2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java @@ -135,6 +135,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.datasync.HuaweiDataSyncFeatureManager; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.datasync.HuaweiDataSyncEmotion; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.datasync.HuaweiDataSyncFindDevice; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.datasync.HuaweiDataSyncGoals; @@ -158,6 +159,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetW import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWorkoutCapability; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWorkoutTotalsRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendCameraRemoteSetupEvent; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendCountryCodeRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendDeviceReportThreshold; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendExtendedAccountRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFitnessUserInfoRequest; @@ -203,7 +205,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetB import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetConnectStatusRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetDeviceStatusRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetDndLiftWristTypeRequest; -import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetExpandCapabilityRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetLinkParamsRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetPincodeRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetProductInformationRequest; @@ -296,6 +297,8 @@ public class HuaweiSupportProvider { protected HuaweiDataSyncManager huaweiDataSyncManager = new HuaweiDataSyncManager(this); + private HuaweiDataSyncFeatureManager huaweiDataSyncFeatureManager = null; + private HuaweiDataSyncGoals huaweiDataSyncTreeCircleGoals = null; private HuaweiDataSyncFindDevice huaweiDataSyncFindDevice = null; @@ -845,9 +848,26 @@ public class HuaweiSupportProvider { */ public void initializeDynamicServices() { try { + + // NOTE: register all DAta Sync handlers on the early stage. We can receive requests from the watch during initialization. + if(getHuaweiCoordinator().getSendCountryCodeEnabled(getDevice())) { + huaweiDataSyncFeatureManager = new HuaweiDataSyncFeatureManager(HuaweiSupportProvider.this); + } + + if (getHuaweiCoordinator().supportsThreeCircle() || getHuaweiCoordinator().supportsThreeCircleLite()) { + huaweiDataSyncTreeCircleGoals = new HuaweiDataSyncGoals(HuaweiSupportProvider.this); + } + + if (getHuaweiCoordinator().supportsFindDeviceAbility()) { + huaweiDataSyncFindDevice = new HuaweiDataSyncFindDevice(HuaweiSupportProvider.this); + } + + if (getHuaweiCoordinator().supportsEmotion()) { + huaweiDataSyncEmotion = new HuaweiDataSyncEmotion(HuaweiSupportProvider.this); + } + // All of the below check that they are supported and otherwise they skip themselves final List initRequestQueue = new ArrayList<>(); - initRequestQueue.add(new GetExpandCapabilityRequest(this)); initRequestQueue.add(new SendExtendedAccountRequest(this)); initRequestQueue.add(new GetSettingRelatedRequest(this)); initRequestQueue.add(new AcceptAgreementsRequest(this)); @@ -890,6 +910,7 @@ public class HuaweiSupportProvider { initRequestQueue.add(new GetContactsCount(this)); initRequestQueue.add(new SendOTASetAutoUpdate(this)); initRequestQueue.add(new GetOTAChangeLog(this)); + initRequestQueue.add(new SendCountryCodeRequest(this)); initRequestQueue.add(new GetWorkoutCapability(this)); initRequestQueue.add(new GetEventAlarmList(this)); initRequestQueue.add(new GetSmartAlarmList(this)); @@ -951,18 +972,6 @@ public class HuaweiSupportProvider { } } } - - if (getHuaweiCoordinator().supportsThreeCircle() || getHuaweiCoordinator().supportsThreeCircleLite()) { - huaweiDataSyncTreeCircleGoals = new HuaweiDataSyncGoals(HuaweiSupportProvider.this); - } - - if (getHuaweiCoordinator().supportsFindDeviceAbility()) { - huaweiDataSyncFindDevice = new HuaweiDataSyncFindDevice(HuaweiSupportProvider.this); - } - - if (getHuaweiCoordinator().supportsEmotion()) { - huaweiDataSyncEmotion = new HuaweiDataSyncEmotion(HuaweiSupportProvider.this); - } } }); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/datasync/HuaweiDataSyncFeatureManager.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/datasync/HuaweiDataSyncFeatureManager.java new file mode 100644 index 0000000000..0fa8116874 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/datasync/HuaweiDataSyncFeatureManager.java @@ -0,0 +1,250 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.datasync; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiUploadManager; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFileUploadInfo; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class HuaweiDataSyncFeatureManager implements HuaweiDataSyncCommon.DataCallback { + private final Logger LOG = LoggerFactory.getLogger(HuaweiDataSyncFeatureManager.class); + + private final HuaweiSupportProvider support; + + // Increase each time when futures changed + private static final int version = 1; + + public static final String SRC_PKG_NAME = "hw.unitedevice.configManager"; + public static final String PKG_NAME = "featureManager"; + + public HuaweiDataSyncFeatureManager(HuaweiSupportProvider support) { + LOG.info("HuaweiDataSyncFeatureManager"); + this.support = support; + this.support.getHuaweiDataSyncManager().registerCallback(PKG_NAME, this); + } + + private boolean sendCommonFeaturesData(byte configAction, byte[] configData) { + + LOG.info("HuaweiDataSyncFeatureManager sendCommonFeaturesData"); + HuaweiDataSyncCommon.ConfigCommandData data = new HuaweiDataSyncCommon.ConfigCommandData(); + HuaweiDataSyncCommon.ConfigData featuresConfigData = new HuaweiDataSyncCommon.ConfigData(); + + featuresConfigData.configId = 900100007; + featuresConfigData.configAction = configAction; + featuresConfigData.configData = configData; + List list = new ArrayList<>(); + list.add(featuresConfigData); + data.setConfigDataList(list); + return this.support.getHuaweiDataSyncManager().sendConfigCommand(SRC_PKG_NAME, PKG_NAME, data); + } + + private JsonObject getPwvDetectionFeature() { + JsonObject res = new JsonObject(); + res.addProperty("devPackageName", "com.huawei.health.pwvdetection"); + res.addProperty("support", 1); + JsonObject extInfo = new JsonObject(); + extInfo.addProperty("medical", false); + res.add("extInfo", extInfo); + return res; + } + + private JsonObject getBloodPressureFeature() { + JsonObject res = new JsonObject(); + res.addProperty("devPackageName", "com.huawei.health.bloodpressure"); + res.addProperty("support", 1); + JsonObject extInfo = new JsonObject(); + JsonArray ABPM = new JsonArray(); + ABPM.add(135); + ABPM.add(85); + ABPM.add(120); + ABPM.add(70); + ABPM.add(130); + ABPM.add(80); + extInfo.add("ABPM", ABPM); + extInfo.addProperty("local", 2); + res.add("extInfo", extInfo); + return res; + } + + private JsonObject getEcgAnalysisFeature() { + JsonObject res = new JsonObject(); + res.addProperty("devPackageName", "com.huawei.watch.health.ecganalysis"); + res.addProperty("support", 1); + JsonObject extInfo = new JsonObject(); + extInfo.addProperty("medical", false); + res.add("extInfo", extInfo); + return res; + } + + private JsonObject getHealthCheckFeature() { + JsonObject res = new JsonObject(); + res.addProperty("devPackageName", "com.huawei.health.healthcheck"); + res.addProperty("support", 1); + JsonObject extInfo = new JsonObject(); + extInfo.addProperty("checkItem", 23); + res.add("extInfo", extInfo); + return res; + } + + private JsonObject getEmotionalFeature() { + JsonObject res = new JsonObject(); + res.addProperty("devPackageName", "com.huawei.hmos.watch.emotional"); + res.addProperty("support", 1); + return res; + } + + private JsonObject getPlateauCareFeature() { + JsonObject res = new JsonObject(); + res.addProperty("devPackageName", "com.huawei.hmos.watch.plateaucare"); + res.addProperty("support", 1); + JsonObject extInfo = new JsonObject(); + extInfo.addProperty("medical", false); + res.add("extInfo", extInfo); + return res; + } + + private JsonObject getArrhythmiaFeature() { + JsonObject res = new JsonObject(); + res.addProperty("devPackageName", "com.huawei.watch.health.arrhythmia"); + res.addProperty("support", 1); + JsonObject extInfo = new JsonObject(); + extInfo.addProperty("medical", false); + res.add("extInfo", extInfo); + return res; + } + + private String getFeaturesFileContent(final String countryCode) { + JsonArray featureList = new JsonArray(); + + featureList.add(getPwvDetectionFeature()); + featureList.add(getBloodPressureFeature()); + featureList.add(getEcgAnalysisFeature()); + featureList.add(getHealthCheckFeature()); + featureList.add(getEmotionalFeature()); + featureList.add(getPlateauCareFeature()); + featureList.add(getArrhythmiaFeature()); + + JsonObject featureData = new JsonObject(); + + featureData.add("featureList", featureList); + featureData.addProperty("country", countryCode); + featureData.addProperty("ver", version); + featureData.addProperty("name", String.format(Locale.ROOT, "feature_%s_device", countryCode)); + + return new Gson().toJson(featureData); + } + + private String getNotifyJsonData(final String countryCode) { + JsonArray filterCondition = new JsonArray(); + JsonObject filterConditionItem = new JsonObject(); + filterConditionItem.addProperty("filterCountry", countryCode); + filterCondition.add(filterConditionItem); + + JsonArray fileData = new JsonArray(); + JsonObject fileDataItem = new JsonObject(); + fileDataItem.addProperty("version", version); + fileDataItem.addProperty("fileName", String.format(Locale.ROOT, "feature_%s_device.txt", countryCode)); + fileData.add(fileDataItem); + + JsonObject responseData = new JsonObject(); + responseData.add("filterCondition", filterCondition); + responseData.addProperty("configName", "com.huawei.health_deviceFeature_config"); + responseData.add("fileData", fileData); + + return new Gson().toJson(responseData); + } + + private void sendFeaturesFile() { + LOG.info("HuaweiDataSyncFeatureManager Send feature file upload info"); + + final String countryCode = support.getHuaweiCoordinator().getCountryCode(support.getDevice()); + + final String json = getFeaturesFileContent(countryCode); + LOG.info("feature file content: {}", json); + + HuaweiUploadManager.FileUploadInfo fileInfo = new HuaweiUploadManager.FileUploadInfo(); + + fileInfo.setFileType((byte) 0x0f); + fileInfo.setFileName(String.format(Locale.ROOT, "feature_%s_device.txt", countryCode)); + fileInfo.setBytes(json.getBytes(StandardCharsets.UTF_8)); + + fileInfo.setFileUploadCallback(new HuaweiUploadManager.FileUploadCallback() { + @Override + public void onUploadStart() { + } + + @Override + public void onUploadProgress(int progress) { + } + + @Override + public void onUploadComplete() { + final String json = getNotifyJsonData(countryCode); + LOG.info("Notify file content: {}", json); + sendCommonFeaturesData((byte) 1, json.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void onError(int code) { + if (support.getDevice().isBusy()) { + support.getDevice().unsetBusyTask(); + support.getDevice().sendDeviceUpdateIntent(support.getContext()); + } + } + }); + + HuaweiUploadManager huaweiUploadManager = support.getUploadManager(); + + huaweiUploadManager.setFileUploadInfo(fileInfo); + + try { + SendFileUploadInfo sendFileUploadInfo = new SendFileUploadInfo(support, huaweiUploadManager); + sendFileUploadInfo.doPerform(); + } catch (IOException e) { + LOG.error("Failed to send file upload info", e); + } + } + + + @Override + public void onConfigCommand(HuaweiDataSyncCommon.ConfigCommandData data) { + //TODO: handle this + LOG.info("HuaweiDataSyncConfigManager code: {}", data.getCode()); + if (data.getConfigDataList() != null && !data.getConfigDataList().isEmpty()) { + HuaweiDataSyncCommon.ConfigData dt = data.getConfigDataList().get(0); + LOG.info("HuaweiDataSyncConfigManager config Action: {}, ID: {}, Data: {}", dt.configAction, dt.configId, GB.hexdump(dt.configData)); + if (dt.configAction == 2) { + if (sendCommonFeaturesData((byte) 2, new byte[]{0x30, 0x31})) { + sendFeaturesFile(); + } + } + } + } + + @Override + public void onEventCommand(HuaweiDataSyncCommon.EventCommandData data) { + + } + + @Override + public void onDataCommand(HuaweiDataSyncCommon.DataCommandData data) { + + } + + @Override + public void onDictDataCommand(HuaweiDataSyncCommon.DictDataCommandData data) { + + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExpandCapabilityRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExpandCapabilityRequest.java index 993646eea9..a5f2ec0ec1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExpandCapabilityRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetExpandCapabilityRequest.java @@ -40,10 +40,6 @@ public class GetExpandCapabilityRequest extends Request { this.commandId = DeviceConfig.ExpandCapability.id; } - @Override - protected boolean requestSupported() { - return supportProvider.getHuaweiCoordinator().supportsExpandCapability(); - } @Override protected List createRequest() throws RequestCreationException { @@ -55,6 +51,13 @@ public class GetExpandCapabilityRequest extends Request { } } + RequestCallback dynamicServicesReq = new RequestCallback() { + @Override + public void call() { + supportProvider.initializeDynamicServices(); + } + }; + @Override protected void processResponse() throws ResponseParseException { LOG.debug("handle Expand Capability"); @@ -63,5 +66,7 @@ public class GetExpandCapabilityRequest extends Request { throw new ResponseTypeMismatchException(receivedPacket, DeviceConfig.ExpandCapability.Response.class); supportProvider.getHuaweiCoordinator().saveExpandCapabilities(((DeviceConfig.ExpandCapability.Response) receivedPacket).expandCapabilities); + + dynamicServicesReq.call(); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedCommandsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedCommandsRequest.java index 7342668590..775341c5f5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedCommandsRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/GetSupportedCommandsRequest.java @@ -93,7 +93,12 @@ public class GetSupportedCommandsRequest extends Request { this.nextRequest(nextRequest); } else { supportProvider.getHuaweiCoordinator().printCommandsPerService(); - dynamicServicesReq.call(); + if(supportProvider.getHuaweiCoordinator().supportsExpandCapability()) { + GetExpandCapabilityRequest nextRequest = new GetExpandCapabilityRequest(supportProvider); + this.nextRequest(nextRequest); + } else { + dynamicServicesReq.call(); + } } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendCountryCodeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendCountryCodeRequest.java new file mode 100644 index 0000000000..94864b53a6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/requests/SendCountryCodeRequest.java @@ -0,0 +1,50 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiUtil; +import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.AccountRelated; +import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.HuaweiSupportProvider; + +public class SendCountryCodeRequest extends Request { + private static final Logger LOG = LoggerFactory.getLogger(SendCountryCodeRequest.class); + + public SendCountryCodeRequest(HuaweiSupportProvider support) { + super(support); + this.serviceId = AccountRelated.id; + this.commandId = AccountRelated.SendCountryCodeToDevice.id; + } + + @Override + protected boolean requestSupported() { + return supportProvider.getHuaweiCoordinator().supportsSendCountryCode() && supportProvider.getHuaweiCoordinator().getSendCountryCodeEnabled(supportProvider.getDevice()); + } + + @Override + protected List createRequest() throws Request.RequestCreationException { + try { + final String countryCode = supportProvider.getHuaweiCoordinator().getCountryCode(supportProvider.getDevice()); + Byte siteId = null; + if(supportProvider.getHuaweiCoordinator().supportsSendSiteId()) { + siteId = (byte) HuaweiUtil.getSiteIdByCountryCode(countryCode); + } + + return new AccountRelated.SendCountryCodeToDevice.Request( + paramsProvider, + countryCode, + siteId) + .serialize(); + } catch (HuaweiPacket.CryptoException e) { + throw new Request.RequestCreationException(e); + } + } + + @Override + protected void processResponse() throws Request.ResponseParseException { + LOG.debug("handle Send Country code to Device"); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f099fe447f..0b485946da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4372,4 +4372,13 @@ Lactate threshold heart rate Reset all Are you sure you want to delete the heart rate zones and limits for all posture configurations and reset them to default? + Health features config + Configure extended health features. + Enable additional health features + Enabling the settings below will activate additional health features on your device. For example, emotional or ECG.\nNOTE: The available features depend on the device. + Send country code and features config to the device. + Warning: Enabling these settings when both Gadgetbridge and the vendor app are in use can cause issues with the vendor app and device functionality or can not affect current device abilities at all. Enable at your own risk.\nDevice reconnect is required. + Send country code + Send country code to the device.\n Attention: country code should be set + Country code diff --git a/app/src/main/res/xml/devicesettings_huawei_country_code.xml b/app/src/main/res/xml/devicesettings_huawei_country_code.xml new file mode 100644 index 0000000000..a7c5c1c9b0 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_huawei_country_code.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file