Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huawei/HuaweiSupportProvider.java

1762 lines
80 KiB
Java

/* Copyright (C) 2024 Damien Gaignon, Martin.JM, Vitalii Tomin
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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huawei;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.content.SharedPreferences;
import android.widget.Toast;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiConstants;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCoordinatorSupplier.HuaweiDeviceType;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiCrypto;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiPacket;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Weather;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
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.requests.GetEventAlarmList;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationConstraintsRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSmartAlarmList;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendGpsAndTimeToDeviceRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherCurrentRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyHeartRateCapabilityRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotifyRestHeartRateCapabilityRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherExtendedSupportRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherForecastRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherStartRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherSunMoonSupportRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherSupportRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendWeatherUnitRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticHeartrateRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetAutomaticSpoRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetDisconnectNotification;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetMediumToStrengthThresholdRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.StopFindPhoneRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.StopNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetFitnessTotalsRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetHiChainRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSleepDataCountRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetStepDataCountRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWorkoutCountRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetMusicRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.AlarmsRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.DebugRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetActivityTypeRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendAccountRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.Request.RequestCallback;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetAuthRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetBatteryLevelRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetBondParamsRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetBondRequest;
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;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSecurityNegotiationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSettingRelatedRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetSupportedServicesRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetWearStatusRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendDndAddRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFactoryResetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendFitnessGoalRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendMenstrualCapabilityRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendDndDeleteRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SendSetUpDeviceStatusRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetActivateOnLiftRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetActivityReminderRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetDateFormatRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetLanguageSettingRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetNavigateOnRotateRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetTimeRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetTimeZoneIdRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetTruSleepRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWearLocationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWearMessagePushRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.GetNotificationCapabilitiesRequest;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.FitnessData;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huawei.requests.SetWorkModeRequest;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class HuaweiSupportProvider {
private static final Logger LOG = LoggerFactory.getLogger(HuaweiSupportProvider.class);
private HuaweiBRSupport brSupport;
private HuaweiLESupport leSupport;
private GBDevice gbDevice;
private Context context;
private HuaweiCoordinatorSupplier.HuaweiDeviceType huaweiType;
private boolean needsAuth = false;
protected byte protocolVersion;
public String deviceMac; //get it from GB
protected String macAddress;
protected String androidID;
protected short msgId = 0;
private MusicStateSpec musicStateSpec = null;
private MusicSpec musicSpec = null;
private final HuaweiPacket.ParamsProvider paramsProvider = new HuaweiPacket.ParamsProvider();
protected ResponseManager responseManager = new ResponseManager(this);
public HuaweiCoordinatorSupplier getCoordinator() {
return ((HuaweiCoordinatorSupplier) this.gbDevice.getDeviceCoordinator());
}
public HuaweiCoordinator getHuaweiCoordinator() {
return getCoordinator().getHuaweiCoordinator();
}
public HuaweiSupportProvider(HuaweiBRSupport support) {
this.brSupport = support;
}
public HuaweiSupportProvider(HuaweiLESupport support) {
this.leSupport = support;
}
public boolean isBLE() {
return huaweiType == HuaweiDeviceType.AW || huaweiType == HuaweiDeviceType.BLE || huaweiType == HuaweiDeviceType.SMART;
}
public nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder createLeTransactionBuilder(String taskName) {
return leSupport.createTransactionBuilder(taskName);
}
public nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder createBrTransactionBuilder(String taskName) {
return brSupport.createTransactionBuilder(taskName);
}
public BluetoothGattCharacteristic getLeCharacteristic(UUID uuid) {
return leSupport.getCharacteristic(uuid);
}
public void performConnected(nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction transaction) throws IOException {
leSupport.performConnected(transaction);
}
public void performConnected(nodomain.freeyourgadget.gadgetbridge.service.btbr.Transaction transaction) throws IOException {
brSupport.performConnected(transaction);
}
public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
if (isBLE()) {
leSupport.evaluateGBDeviceEvent(deviceEvent);
} else {
brSupport.evaluateGBDeviceEvent(deviceEvent);
}
}
protected nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder initializeDevice(nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder builder) {
this.gbDevice = leSupport.getDevice();
this.context = leSupport.getContext();
this.huaweiType = getCoordinator().getHuaweiType();
this.paramsProvider.setTransactionsCrypted(this.getHuaweiCoordinator().isTransactionCrypted());
builder.setCallback(leSupport);
builder.notify(leSupport.getCharacteristic(HuaweiConstants.UUID_CHARACTERISTIC_HUAWEI_READ), true);
builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
final GetLinkParamsRequest linkParamsReq = new GetLinkParamsRequest(this, builder);
initializeDevice(linkParamsReq);
getCoordinator().setDevice(this.gbDevice);
return builder;
}
protected nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder initializeDevice(nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder builder) {
this.gbDevice = brSupport.getDevice();
this.context = brSupport.getContext();
this.huaweiType = getCoordinator().getHuaweiType();
this.paramsProvider.setTransactionsCrypted(this.getHuaweiCoordinator().isTransactionCrypted());
builder.setCallback(brSupport);
builder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
final GetLinkParamsRequest linkParamsReq = new GetLinkParamsRequest(this, builder);
initializeDevice(linkParamsReq);
getCoordinator().setDevice(this.gbDevice);
return builder;
}
protected void initializeDevice(Request linkParamsReq) {
deviceMac = this.gbDevice.getAddress();
createRandomMacAddress();
createAndroidID();
try {
RequestCallback finalizeReq = new RequestCallback() {
@Override
public void call() {
initializeDeviceCheckStatus(linkParamsReq);
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Link params TLV exception", e);
}
};
linkParamsReq.setFinalizeReq(finalizeReq);
linkParamsReq.doPerform();
} catch (IOException e) {
GB.toast(context, "Initialization of authenticating to Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Initialization of authenticating to Huawei device failed", e);
}
/* This is to have the setting match the default Huawei behaviour */
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDeviceMac());
if (!sharedPrefs.contains(DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED)) {
sharedPrefs.edit().putBoolean(DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED, true).apply();
}
}
protected void initializeDeviceCheckStatus(Request linkParamsReq) {
try {
GetDeviceStatusRequest deviceStatusReq = new GetDeviceStatusRequest(this, true);
RequestCallback finalizeReq = new RequestCallback() {
@Override
public void call() {
int status = (int)deviceStatusReq.status;
if (status == -0x01 || status == 0x00 || status == 0x01) {
initializeDeviceDealHiChain(linkParamsReq);
} else {
initializeDeviceNotify();
}
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Status TLV exception", e);
}
};
if (huaweiType == HuaweiDeviceType.BLE) { //Only BLE known, check later for AW and SMART
initializeDeviceDealHiChain(linkParamsReq);
} else {
deviceStatusReq.setFinalizeReq(finalizeReq);
deviceStatusReq.doPerform();
}
} catch (IOException e) {
GB.toast(context, "Status of authenticating to Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
e.printStackTrace();
}
}
protected boolean isHiChain() {
// In HH
// HiChain : 1 || 3
// HiChainLite : 2 || 3 || 8
// HiChain3 : 4 & API>=23 - API is always >=23
// For GB we will consider for authMode
// 0 : No HiChain
// 1 or 3 : HiChain
// 2 or 8 : HiChainLite -> normal mode
// 4 : HiChain3
byte authMode = paramsProvider.getDeviceSupportType();
return authMode == 0x01 || authMode == 0x03 || authMode == 0x04 || isHiChainLite();
}
protected boolean isHiChainLite() {
byte authMode = paramsProvider.getDeviceSupportType();
return authMode == 0x02;
}
protected boolean isHiChain3(int authType) {
return (authType ^ 0x01) == 0x04 || (authType ^ 0x02) == 0x04;
}
protected void initializeDeviceDealHiChain(Request linkParamsReq) {
try {
if (isHiChain()) {
GetSecurityNegotiationRequest securityNegoReq = new GetSecurityNegotiationRequest(this);
RequestCallback securityFinalizeReq = new RequestCallback(this) {
@Override
public void call() {
if (securityNegoReq.authType == 0x0186A0 || isHiChain3(securityNegoReq.authType)) {
LOG.debug("HiChain mode");
initializeDeviceHiChainMode(linkParamsReq);
} else if (securityNegoReq.authType == 0x01 || securityNegoReq.authType == 0x02) {
LOG.debug("HiChain Lite mode");
// Keep track the gadget is connected
initializeDeviceHiChainLiteMode(linkParamsReq);
}
}
};
securityNegoReq.setFinalizeReq(securityFinalizeReq);
securityNegoReq.doPerform();
} else {
LOG.debug("Normal mode");
initializeDeviceNormalMode(linkParamsReq);
}
} catch (IOException e) {
// TODO: use translatable string
GB.toast(context, "init Deal with HiChain of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Step of authenticating to Huawei device failed", e);
}
}
protected void initializeDeviceNotify() {} //TODO
RequestCallback configureReq = new RequestCallback() {
@Override
public void call() {
initializeDeviceConfigure();
}
};
protected void initializeDeviceHiChainMode(Request linkParamsReq) {
try {
GetHiChainRequest hiChainReq = new GetHiChainRequest(this, needsAuth);
hiChainReq.setFinalizeReq(configureReq);
if (((GetLinkParamsRequest)linkParamsReq).bondState == 0x00 || ((GetLinkParamsRequest)linkParamsReq).bondState == 0x02) {
GetPincodeRequest pincodeReq = new GetPincodeRequest(this);
pincodeReq.nextRequest(hiChainReq);
pincodeReq.doPerform();
} else
hiChainReq.doPerform();
} catch (IOException e) {
GB.toast(context, "init HiCHain Mode of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
e.printStackTrace();
}
}
protected void initializeDeviceHiChainLiteMode(Request linkParamsReq) {
try {
createSecretKey();
GetAuthRequest authReq = new GetAuthRequest(this, linkParamsReq);
GetBondParamsRequest bondParamsReq = new GetBondParamsRequest(this);
GetBondRequest bondReq = new GetBondRequest(this);
authReq.nextRequest(bondParamsReq);
bondParamsReq.nextRequest(bondReq);
bondParamsReq.setFinalizeReq(configureReq);
bondReq.setFinalizeReq(configureReq);
if (paramsProvider.getPinCode() == null & paramsProvider.getAuthVersion() != 0x02) {
GetPincodeRequest pinCodeReq = new GetPincodeRequest(this);
pinCodeReq.nextRequest(authReq);
pinCodeReq.doPerform();
} else {
authReq.doPerform();
}
} catch (IOException e) {
GB.toast(context, "init HiCHainLite Mode Mode of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
e.printStackTrace();
}
}
protected void initializeDeviceNormalMode(Request linkParamsReq) {
try {
createSecretKey();
GetAuthRequest authReq = new GetAuthRequest(this, linkParamsReq);
if (getHuaweiType() == HuaweiDeviceType.BLE || getHuaweiType() == HuaweiDeviceType.AW) {
GetBondParamsRequest bondParamsReq = new GetBondParamsRequest(this);
GetBondRequest bondReq = new GetBondRequest(this);
authReq.nextRequest(bondParamsReq);
bondParamsReq.nextRequest(bondReq);
bondParamsReq.setFinalizeReq(configureReq);
bondReq.setFinalizeReq(configureReq);
} else {
authReq.setFinalizeReq(configureReq);
}
authReq.doPerform();
} catch (IOException e) {
GB.toast(context, "init Normal Mode of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
e.printStackTrace();
}
}
protected void initializeDeviceConfigure() {
nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder leBuilder = null;
nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder brBuilder = null;
if (isBLE()) {
leBuilder = createLeTransactionBuilder("Initializing");
leBuilder.setCallback(leSupport);
leBuilder.notify(leSupport.getCharacteristic(HuaweiConstants.UUID_CHARACTERISTIC_HUAWEI_READ), true);
leBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction(gbDevice, GBDevice.State.INITIALIZING, context));
} else {
brBuilder = createBrTransactionBuilder("Initializing");
brBuilder.setCallback(brSupport);
brBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(gbDevice, GBDevice.State.INITIALIZING, context));
}
try {
GetProductInformationRequest productInformationReq = new GetProductInformationRequest(this);
Request setTimeReq = setTime();
GetSupportedServicesRequest supportedServicesReq = new GetSupportedServicesRequest(this);
productInformationReq.nextRequest(setTimeReq);
setTimeReq.nextRequest(supportedServicesReq);
productInformationReq.doPerform();
if (needsAuth) {
// Workaround to enable PREF_HUAWEI_ROTATE_WRIST_TO_SWITCH_INFO preference
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT, "p_on");
editor.apply();
// initializeAlarms();
setNavigateOnRotate();
setTrusleep();
}
onSetTime();
getBatteryLevel();
if (isBLE()) {
assert leBuilder != null;
leBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction(gbDevice, GBDevice.State.INITIALIZED, context));
leSupport.performConnected(leBuilder.getTransaction());
} else {
assert brBuilder != null;
brBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceStateAction(gbDevice, GBDevice.State.INITIALIZED, context));
brSupport.performConnected(brBuilder.getTransaction());
}
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Final initialization of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Final initialization of Huawei device failed", e);
}
}
public void createSecretKey() {
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
String authKey = sharedPrefs.getString("authkey", null);
if (authKey == null || authKey.isEmpty()) {
SharedPreferences.Editor editor = sharedPrefs.edit();
authKey = StringUtils.bytesToHex(HuaweiCrypto.generateNonce());
editor.putString("authkey", authKey);
editor.apply();
}
paramsProvider.setSecretKey(GB.hexStringToByteArray(authKey));
}
public byte[] getSecretKey() {
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
String authKey = sharedPrefs.getString("authkey", null);
return GB.hexStringToByteArray(authKey);
}
public void setSecretKey(byte[] authKey) {
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString("authkey", StringUtils.bytesToHex(authKey));
editor.apply();
paramsProvider.setSecretKey(authKey);
}
public HuaweiCoordinatorSupplier.HuaweiDeviceType getHuaweiType() {
return this.huaweiType;
}
public HuaweiPacket.ParamsProvider getParamsProvider() {
return paramsProvider;
}
public void setNeedsAuth(boolean needsAuth) {
this.needsAuth = needsAuth;
}
protected void createRandomMacAddress() {
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
macAddress = sharedPrefs.getString(HuaweiConstants.PREF_HUAWEI_ADDRESS, null);
if (macAddress == null || macAddress.isEmpty()) {
StringBuilder mac = new StringBuilder("FF:FF:FF");
Random r = new Random();
for (int i = 0; i < 3; i++) {
int n = r.nextInt(255);
mac.append(String.format(":%02x", n));
}
macAddress = mac.toString().toUpperCase();
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(HuaweiConstants.PREF_HUAWEI_ADDRESS, macAddress);
editor.apply();
}
}
public byte[] getMacAddress() {
return macAddress.getBytes(StandardCharsets.UTF_8);
}
public byte[] getSerial() {
return macAddress.replace(":", "").substring(6, 12).getBytes(StandardCharsets.UTF_8);
}
public String getDeviceMac() {
return deviceMac;
}
protected void createAndroidID() {
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
androidID = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_FAKE_ANDROID_ID, null);
if (androidID == null || androidID.isEmpty()) {
androidID = StringUtils.bytesToHex(HuaweiCrypto.generateNonce());
LOG.debug("Created androidID: " + androidID);
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString(DeviceSettingsPreferenceConst.PREF_FAKE_ANDROID_ID, androidID);
editor.apply();
}
}
public byte[] getAndroidId() {
return androidID.getBytes(StandardCharsets.UTF_8);
}
public Context getContext() {
return context;
}
public GBDevice getDevice() {
return gbDevice;
}
/**
* Initialize the services that may or may not be supported on the device
* To be called after the commandsPerService is filled in the coordinator
*/
public void initializeDynamicServices() {
// Setup the alarms
if (!getHuaweiCoordinator().supportsChangingAlarm()) {
if (needsAuth) {
// TODO: not really sure if this is necessary, but it probably won't do any harm
initializeAlarms();
}
} else {
getAlarms();
}
try {
if (getHuaweiCoordinator().supportsExpandCapability()) {
GetExpandCapabilityRequest expandCapabilityReq = new GetExpandCapabilityRequest(this);
expandCapabilityReq.doPerform();
}
if (getHuaweiCoordinator().supportsAccount()) { // GetAccountJudgment
SendAccountRequest sendAccountReq = new SendAccountRequest(this);
sendAccountReq.doPerform();
}
if (getHuaweiCoordinator().supportsActivityType()) {
GetActivityTypeRequest activityTypeReq = new GetActivityTypeRequest(this);
activityTypeReq.doPerform();
}
if (getHuaweiCoordinator().supportsSettingRelated()) { // GetSettingRelated
GetSettingRelatedRequest getSettingRelatedReq = new GetSettingRelatedRequest(this);
getSettingRelatedReq.doPerform();
}
if (getHuaweiCoordinator().supportsConnectStatus()) {
GetConnectStatusRequest getConnectStatusReq = new GetConnectStatusRequest(this);
getConnectStatusReq.doPerform();
}
if (getHuaweiCoordinator().supportsActivateOnLift()) {
setActivateOnLift();
}
if (getHuaweiCoordinator().supportsWearLocation(getDevice())) {
setWearLocation();
}
if (getHuaweiCoordinator().supportsQueryDndLiftWristDisturbType()) {
GetDndLiftWristTypeRequest getDndLiftWristTypeReq = new GetDndLiftWristTypeRequest(this);
getDndLiftWristTypeReq.doPerform();
}
if (getHuaweiCoordinator().supportsDoNotDisturb(gbDevice)) {
SendDndDeleteRequest sendDndDeleteReq = new SendDndDeleteRequest(this);
SendDndAddRequest sendDndAddReq = new SendDndAddRequest(this);
sendDndDeleteReq.nextRequest(sendDndAddReq);
sendDndDeleteReq.doPerform();
}
if (getHuaweiCoordinator().supportsNotification()) { // 0x02 - 0x04
setNotificationStatus();
}
if (getHuaweiCoordinator().supportsDoNotDisturb(gbDevice) && getHuaweiCoordinator().supportsWearMessagePush()) {
setDndNotWear();
}
if (getHuaweiCoordinator().supportsTimeAndZoneId()) {
setTimeZoneId();
}
// Nothing usefull yet with this requests
if (getHuaweiCoordinator().supportsMultiDevice()) {
SendSetUpDeviceStatusRequest sendSetUpDeviceStatusReq = new SendSetUpDeviceStatusRequest(this);
sendSetUpDeviceStatusReq.doPerform();
GetWearStatusRequest getWearStatusReq = new GetWearStatusRequest(this);
getWearStatusReq.doPerform();
}
if (getHuaweiCoordinator().supportsMenstrual()) {
SendMenstrualCapabilityRequest sendMenstrualCapabilityReq = new SendMenstrualCapabilityRequest(this);
sendMenstrualCapabilityReq.doPerform();
}
if (getHuaweiCoordinator().supportsLanguageSetting()) { // 0x0c - 0x01
setLanguageSetting();
}
if (getHuaweiCoordinator().supportsWorkoutsTrustHeartRate()) {
SendNotifyHeartRateCapabilityRequest sendNotifyHeartRateCapabilityReq = new SendNotifyHeartRateCapabilityRequest(this);
sendNotifyHeartRateCapabilityReq.doPerform();
}
if (getHuaweiCoordinator().supportsFitnessRestHeartRate()) {
SendNotifyRestHeartRateCapabilityRequest sendNotifyRestHeartRateCapabilityReq = new SendNotifyRestHeartRateCapabilityRequest(this);
sendNotifyRestHeartRateCapabilityReq.doPerform();
}
if (getHuaweiCoordinator().supportsFitnessThresholdValue()) {
SetMediumToStrengthThresholdRequest setMediumToStrengthThresholdReq = new SetMediumToStrengthThresholdRequest(this);
setMediumToStrengthThresholdReq.doPerform();
}
if (getHuaweiCoordinator().supportsDateFormat()) { //0x01 - 0x04
setDateFormat();
}
if (getHuaweiCoordinator().supportsMotionGoal()) {
SendFitnessGoalRequest sendFitnessGoalReq = new SendFitnessGoalRequest(this);
sendFitnessGoalReq.doPerform();
}
if (getHuaweiCoordinator().supportsActivityReminder()) {
setActivityReminder();
}
if (getHuaweiCoordinator().supportsPromptPushMessage() && getProtocolVersion() == 2) {
GetNotificationCapabilitiesRequest getNotificationCapabilitiesReq = new GetNotificationCapabilitiesRequest(this);
getNotificationCapabilitiesReq.doPerform();
}
if (getHuaweiCoordinator().supportsNotificationAlert() && getProtocolVersion() == 2) {
GetNotificationConstraintsRequest getNotificationConstraintsReq = new GetNotificationConstraintsRequest(this);
getNotificationConstraintsReq.doPerform();
}
} catch (IOException e) {
GB.toast(getContext(), "Initialize dynamic services of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR,
e);
e.printStackTrace();
}
// Properly update the device card
gbDevice.sendDeviceUpdateIntent(GBApplication.getContext());
GB.signalActivityDataFinish();
}
public void setProtocolVersion(byte protocolVersion) {
this.protocolVersion = protocolVersion;
}
public byte getProtocolVersion() {
return this.protocolVersion;
}
private void initializeAlarms() {
// TODO: check for smart alarm && overwrite for smart alarm
// note that lowering the alarm count shouldn't delete the alarm of course...
// Populate alarms in order to specify important data
List<Alarm> alarms = DBHelper.getAlarms(gbDevice);
DeviceCoordinator coordinator = this.gbDevice.getDeviceCoordinator();
int supportedNumAlarms = coordinator.getAlarmSlotCount(gbDevice);
if (alarms.size() == 0) {
try (DBHandler db = GBApplication.acquireDB()) {
DaoSession daoSession = db.getDaoSession();
Device device = DBHelper.getDevice(gbDevice, daoSession);
User user = DBHelper.getUser(daoSession);
for (int position = 0; position < supportedNumAlarms; position++) {
LOG.info("adding missing alarm at position " + position);
DBHelper.store(createDefaultAlarm(device, user, position));
}
} catch (Exception e) {
// TODO: show user?
// TODO: What exceptions can happen here?
LOG.error("Error accessing database", e);
}
}
}
private Alarm createDefaultAlarm(@NonNull Device device, @NonNull User user, int position) {
boolean smartWakeup = false;
String title = context.getString(R.string.menuitem_alarm);
String description = context.getString(R.string.huawei_alarm_event_description);
if (position == 0) {
smartWakeup = true;
title = context.getString(R.string.alarm_smart_wakeup);
description = context.getString(R.string.huawei_alarm_smart_description);
}
return new Alarm(device.getId(), user.getId(), position, false, smartWakeup, null, false, 0, 6, 30, true, title, description);
}
private void getAlarms() {
if (!getHuaweiCoordinator().supportsChangingAlarm())
return;
GetEventAlarmList getEventAlarmList = new GetEventAlarmList(this);
responseManager.addHandler(getEventAlarmList);
getEventAlarmList.setFinalizeReq(new RequestCallback() {
@Override
public void call() {
if (!getHuaweiCoordinator().supportsSmartAlarm(getDevice()))
return; // Don't get smart alarms when not supported
GetSmartAlarmList getSmartAlarmList = new GetSmartAlarmList(HuaweiSupportProvider.this);
responseManager.addHandler(getSmartAlarmList);
try {
getSmartAlarmList.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Error sending smart alarm list request", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Error sending smart alarm list request", e);
}
}
@Override
public void handleException(Request.ResponseParseException e) {
// TODO: Use translatable string
GB.toast(context, "Error parsing event list", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Error parsing event list", e);
}
});
try {
getEventAlarmList.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Error sending event alarm list request", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Error sending event alarm list request", e);
}
}
public void saveAlarms(Alarm[] alarms) {
try (DBHandler db = GBApplication.acquireDB()) {
DaoSession daoSession = db.getDaoSession();
Device device = DBHelper.getDevice(gbDevice, daoSession);
User user = DBHelper.getUser(daoSession);
for (Alarm alarm : alarms) {
alarm.setDeviceId(device.getId());
alarm.setUserId(user.getId());
DBHelper.store(alarm);
}
} catch (Exception e) {
// TODO: Use translatable string
GB.toast(context, "Error saving alarms", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Error saving alarms", e);
}
}
public boolean onCharacteristicChanged(BluetoothGattCharacteristic characteristic) {
byte[] data = characteristic.getValue();
responseManager.handleData(data);
return true;
}
public void onSocketRead(byte[] data) {
//Check multiple packet in data
ByteBuffer bData = ByteBuffer.wrap(data);
while (bData.remaining() != 0x00) {
int dataLen = bData.getShort(bData.position() + 1) + 0x05; // magic + len + CRC
byte[] newData = new byte[dataLen];
bData.get(newData, 0, dataLen);
responseManager.handleData(newData);
}
}
public void removeInProgressRequests(Request req) {
responseManager.removeHandler(req);
}
public void onSendConfiguration(String config) {
try {
switch (config) {
case DeviceSettingsPreferenceConst.PREF_DATEFORMAT:
case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT: {
setDateFormat();
break;
}
case SettingsActivity.PREF_MEASUREMENT_SYSTEM:
case DeviceSettingsPreferenceConst.PREF_LANGUAGE: {
setLanguageSetting();
break;
}
case DeviceSettingsPreferenceConst.PREF_WEARLOCATION: {
setWearLocation();
break;
}
case DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED: {
setActivateOnLift();
break;
}
case MiBandConst.PREF_MI2_ROTATE_WRIST_TO_SWITCH_INFO: {
setNavigateOnRotate();
break;
}
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_ENABLE:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_THRESHOLD:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_START:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_END:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_MO:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_TU:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_WE:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_TH:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_FR:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_SA:
case DeviceSettingsPreferenceConst.PREF_INACTIVITY_SU: {
setActivityReminder();
break;
}
case HuaweiConstants.PREF_HUAWEI_TRUSLEEP: {
setTrusleep();
break;
}
case DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE: {
setNotificationStatus();
break;
}
case HuaweiConstants.PREF_HUAWEI_WORKMODE:
SetWorkModeRequest setWorkModeReq = new SetWorkModeRequest(this);
setWorkModeReq.doPerform();
break;
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_START:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_END:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_MO:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_TU:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_WE:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_TH:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_FR:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SA:
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SU: {
setDnd();
break;
}
case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOT_WEAR:
setDndNotWear();
break;
case DeviceSettingsPreferenceConst.PREF_FIND_PHONE:
case DeviceSettingsPreferenceConst.PREF_FIND_PHONE_DURATION:
// TODO: enable/disable the find phone applet on band
break;
case DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED:
setDisconnectNotification();
break;
case DeviceSettingsPreferenceConst.PREF_HEARTRATE_AUTOMATIC_ENABLE:
setHeartrateAutomatic();
break;
case DeviceSettingsPreferenceConst.PREF_SPO_AUTOMATIC_ENABLE:
setSpoAutomatic();
break;
case DeviceSettingsPreferenceConst.PREF_FORCE_ENABLE_SMART_ALARM:
getAlarms();
break;
case HuaweiConstants.PREF_HUAWEI_DEBUG_REQUEST:
sendDebugRequest();
break;
case ActivityUser.PREF_USER_STEPS_GOAL:
new SendFitnessGoalRequest(this).doPerform();
break;
}
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Configuration of Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Configuration of Huawei device failed", e);
// TODO: handle this?
}
}
public void onFetchRecordedData(int dataTypes) {
if (gbDevice.isBusy()) {
LOG.warn("Device is already busy with " + gbDevice.getBusyTask() + ", so won't fetch data now.");
// TODO: better way of letting user know?
// TODO: use string that can be translated
GB.toast("Device is already busy with " + gbDevice.getBusyTask() + ", so won't fetch data now.", Toast.LENGTH_LONG, 0);
return;
}
// TODO: An exception during the parsing can leave GB thinking that the sync is not yet
// finished, but it won't ever complete because of the parsing exception
// Maybe this can be fixed with an exception handler from the callback? If then
// called from the ResponseManager, it may not be too much work to implement.
if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) {
fetchActivityData();
} else if (dataTypes == RecordedDataTypes.TYPE_GPS_TRACKS) {
fetchWorkoutData();
} else {
// TODO: tell user
LOG.warn("Recorded data type {} not implemented yet.", dataTypes);
}
// Get the battery level, as that isn't shared nicely for now
getBatteryLevel();
// Get the alarms as they cannot be retrieved on opening the alarm window
// TODO: get the alarms if the alarm settings are opened instead of here
getAlarms();
}
private void fetchActivityData() {
int sleepStart = 0;
int stepStart = 0;
int end = (int) (System.currentTimeMillis() / 1000);
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
long prefLastSyncTime = sharedPreferences.getLong("lastSyncTimeMillis", 0);
if (prefLastSyncTime != 0) {
sleepStart = (int) (prefLastSyncTime / 1000);
stepStart = (int) (prefLastSyncTime / 1000);
// Reset for next calls
sharedPreferences.edit().putLong("lastSyncTimeMillis", 0).apply();
} else {
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
sleepStart = sampleProvider.getLastSleepFetchTimestamp();
stepStart = sampleProvider.getLastStepFetchTimestamp();
} catch (Exception e) {
LOG.warn("Exception for getting start times, using 01/01/2000 - 00:00:00.");
}
// Some bands don't work with zero timestamp, so starting later
if (sleepStart == 0)
sleepStart = 946684800;
if (stepStart == 0)
stepStart = 946684800;
}
final GetSleepDataCountRequest getSleepDataCountRequest;
if (isBLE()) {
nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder leBuilder = createLeTransactionBuilder("FetchRecordedData");
leBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction(gbDevice, context.getString(R.string.busy_task_fetch_activity_data), context));
getSleepDataCountRequest = new GetSleepDataCountRequest(this, leBuilder, sleepStart, end);
} else {
nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder brBuilder = createBrTransactionBuilder("FetchRecordedData");
brBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceBusyAction(gbDevice, context.getString(R.string.busy_task_fetch_activity_data), context));
getSleepDataCountRequest = new GetSleepDataCountRequest(this, brBuilder, sleepStart, end);
}
final GetStepDataCountRequest getStepDataCountRequest = new GetStepDataCountRequest(this, stepStart, end);
final GetFitnessTotalsRequest getFitnessTotalsRequest = new GetFitnessTotalsRequest(this);
getFitnessTotalsRequest.setFinalizeReq(new RequestCallback() {
@Override
public void call() {
handleSyncFinished();
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Fitness totals exception", e);
handleSyncFinished();
}
});
getStepDataCountRequest.setFinalizeReq(new RequestCallback() {
@Override
public void call() {
try {
getFitnessTotalsRequest.doPerform();
} catch (IOException e) {
LOG.error("Exception on starting fitness totals request", e);
handleSyncFinished();
}
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Step data count exception", e);
handleSyncFinished();
}
});
getSleepDataCountRequest.setFinalizeReq(new RequestCallback() {
@Override
public void call() {
try {
getStepDataCountRequest.doPerform();
} catch (IOException e) {
LOG.error("Exception on starting step data count request", e);
handleSyncFinished();
}
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Sleep data count exception", e);
handleSyncFinished();
}
});
try {
getSleepDataCountRequest.doPerform();
} catch (IOException e) {
LOG.error("Exception on starting sleep data count request", e);
handleSyncFinished();
}
}
private void fetchWorkoutData() {
int start = 0;
int end = (int) (System.currentTimeMillis() / 1000);
SharedPreferences sharedPreferences = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
long prefLastSyncTime = sharedPreferences.getLong("lastSportsActivityTimeMillis", 0);
if (prefLastSyncTime != 0) {
start = (int) (prefLastSyncTime / 1000);
// Reset for next calls
sharedPreferences.edit().putLong("lastSportsActivityTimeMillis", 0).apply();
} else {
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
QueryBuilder<HuaweiWorkoutSummarySample> qb1 = db.getDaoSession().getHuaweiWorkoutSummarySampleDao().queryBuilder().where(
HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId),
HuaweiWorkoutSummarySampleDao.Properties.UserId.eq(userId)
).orderDesc(
HuaweiWorkoutSummarySampleDao.Properties.StartTimestamp
).limit(1);
List<HuaweiWorkoutSummarySample> samples1 = qb1.list();
if (!samples1.isEmpty())
start = samples1.get(0).getEndTimestamp();
QueryBuilder<BaseActivitySummary> qb2 = db.getDaoSession().getBaseActivitySummaryDao().queryBuilder().where(
BaseActivitySummaryDao.Properties.DeviceId.eq(deviceId),
BaseActivitySummaryDao.Properties.UserId.eq(userId)
).orderDesc(
BaseActivitySummaryDao.Properties.StartTime
).limit(1);
List<BaseActivitySummary> samples2 = qb2.list();
if (!samples2.isEmpty())
start = Math.min(start, (int) (samples2.get(0).getEndTime().getTime() / 1000L));
start = start + 1;
} catch (Exception e) {
LOG.warn("Exception for getting start time, using 10/06/2022 - 00:00:00.");
}
if (start == 0 || start == 1)
start = 1654819200;
}
final GetWorkoutCountRequest getWorkoutCountRequest;
if (isBLE()) {
nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder leBuilder = createLeTransactionBuilder("FetchWorkoutData");
// TODO: maybe use a different string from the other synchronization
leBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction(gbDevice, context.getString(R.string.busy_task_fetch_activity_data), context));
getWorkoutCountRequest = new GetWorkoutCountRequest(this, leBuilder, start, end);
} else {
nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder brBuilder = createBrTransactionBuilder("FetchWorkoutData");
// TODO: maybe use a different string from the other synchronization
brBuilder.add(new nodomain.freeyourgadget.gadgetbridge.service.btbr.actions.SetDeviceBusyAction(gbDevice, context.getString(R.string.busy_task_fetch_activity_data), context));
getWorkoutCountRequest = new GetWorkoutCountRequest(this, brBuilder, start, end);
}
getWorkoutCountRequest.setFinalizeReq(new RequestCallback() {
@Override
public void call() {
handleSyncFinished();
}
@Override
public void handleException(Request.ResponseParseException e) {
LOG.error("Workout parsing exception", e);
handleSyncFinished();
}
});
try {
getWorkoutCountRequest.doPerform();
} catch (IOException e) {
LOG.error("Exception on starting workout count request", e);
handleSyncFinished();
}
}
private void handleSyncFinished() {
if (gbDevice.isBusy()) {
gbDevice.unsetBusyTask();
gbDevice.sendDeviceUpdateIntent(context);
}
GB.signalActivityDataFinish();
}
public void onReset(int flags) {
try {
if(flags== GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) {
SendFactoryResetRequest sendFactoryResetReq = new SendFactoryResetRequest(this);
sendFactoryResetReq.doPerform();
}
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Factory resetting Huawei device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Factory resetting Huawei device failed", e);
}
}
public void setNotificationStatus() {
/*
* TODO: this doesn't work as expected
* We thought it would disable(/enable) the notifications on the device side,
* but at least the disabling doesn't work - so we don't send notifications to the
* device at all if the setting is disabled now.
* TRYING to debug this as it should really be handled on device side...
*/
try {
SetNotificationRequest setNotificationReq = new SetNotificationRequest(this);
setNotificationReq.doPerform();
// SetWearMessagePushRequest setWearMessagePushReq = new SetWearMessagePushRequest(this);
// setWearMessagePushReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Setting notification failed", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Setting notification failed", e);
}
}
public short getNotificationId() {
if (msgId < 256) {
msgId += 1;
} else {
msgId = 0;
}
return msgId;
}
public void onNotification(NotificationSpec notificationSpec) {
if (!GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false)) {
// Don't send notifications when they are disabled
LOG.info("Stopped notification as they are disabled.");
return;
}
SendNotificationRequest sendNotificationReq = new SendNotificationRequest(this);
try {
sendNotificationReq.buildNotificationTLVFromNotificationSpec(notificationSpec);
sendNotificationReq.doPerform();
} catch (IOException e) {
LOG.error("Sending notification failed", e);
}
}
public void setDateFormat() {
try {
SetDateFormatRequest setDateFormatReq = new SetDateFormatRequest(this);
setDateFormatReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to configure date format", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to configure date format", e);
}
}
public void onSetTime() {
try {
setTime().doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to configure time", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to configure time", e);
}
}
private Request setTime() {
SetTimeRequest setTimeReq = new SetTimeRequest(this);
return setTimeReq;
}
public void setTimeZoneId() {
try {
SetTimeZoneIdRequest setTimeZoneIdReq = new SetTimeZoneIdRequest(this);
setTimeZoneIdReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to configure time and zoneId", Toast.LENGTH_SHORT, GB.ERROR, e);
}
}
public void onSetAlarms(ArrayList<? extends nodomain.freeyourgadget.gadgetbridge.model.Alarm> alarms) {
boolean smartAlarmEnabled = getHuaweiCoordinator().supportsSmartAlarm(getDevice());
AlarmsRequest smartAlarmReq = new AlarmsRequest(this, true);
AlarmsRequest eventAlarmReq = new AlarmsRequest(this, false);
for (nodomain.freeyourgadget.gadgetbridge.model.Alarm alarm : alarms) {
if (alarm.getPosition() == 0 && smartAlarmEnabled) {
smartAlarmReq.buildSmartAlarm(alarm);
} else {
eventAlarmReq.addEventAlarm(alarm, !smartAlarmEnabled);
}
}
try {
if (smartAlarmEnabled)
smartAlarmReq.doPerform();
eventAlarmReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to configure alarms", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to configure alarms", e);
}
}
public void onSetCallState(CallSpec callSpec) {
if (callSpec.command == CallSpec.CALL_INCOMING) {
SendNotificationRequest sendNotificationReq = new SendNotificationRequest(this);
try {
sendNotificationReq.buildNotificationTLVFromCallSpec(callSpec);
sendNotificationReq.doPerform();
} catch (IOException e) {
LOG.error("Failed to send start call notification", e);
}
} else if (
callSpec.command == CallSpec.CALL_ACCEPT ||
callSpec.command == CallSpec.CALL_START ||
callSpec.command == CallSpec.CALL_REJECT ||
callSpec.command == CallSpec.CALL_END
) {
StopNotificationRequest stopNotificationRequest = new StopNotificationRequest(this);
try {
stopNotificationRequest.doPerform();
} catch (IOException e) {
LOG.error("Failed to send stop call notification", e);
}
}
}
public void onSetMusicState(MusicStateSpec stateSpec) {
this.musicStateSpec = stateSpec;
sendSetMusic();
}
public void onSetMusicInfo(MusicSpec musicSpec) {
this.musicSpec = musicSpec;
sendSetMusic();
}
public void onSetPhoneVolume() {
// TODO: check when implemented in GB
// We get the audio volume manually, so ignoring the argument
sendSetMusic();
}
public void sendSetMusic() {
// This often gets called twice in a row because of onSetMusicState and onSetMusicInfo
// Maybe we can consolidate that into just one request?
SetMusicRequest setMusicRequest = new SetMusicRequest(this, this.musicStateSpec, this.musicSpec);
try {
setMusicRequest.doPerform();
} catch (IOException e) {
LOG.error("Failed to send set music request", e);
}
}
public void addInProgressRequest(Request request) {
responseManager.addHandler(request);
}
public void addSleepActivity(int timestamp, short duration, byte type) {
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
HuaweiActivitySample activitySample = new HuaweiActivitySample(
timestamp,
deviceId,
userId,
timestamp + duration,
FitnessData.MessageData.sleepId,
type,
1,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED,
ActivitySample.NOT_MEASURED
);
activitySample.setProvider(sampleProvider);
sampleProvider.addGBActivitySample(activitySample);
} catch (Exception e) {
LOG.error("Failed to add sleep activity to database", e);
}
}
public void addStepData(int timestamp, short steps, short calories, short distance, byte spo, byte heartrate) {
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
HuaweiSampleProvider sampleProvider = new HuaweiSampleProvider(gbDevice, db.getDaoSession());
HuaweiActivitySample activitySample = new HuaweiActivitySample(
timestamp,
deviceId,
userId,
timestamp + 60,
FitnessData.MessageData.stepId,
ActivitySample.NOT_MEASURED,
1,
steps,
calories,
distance,
spo,
heartrate
);
activitySample.setProvider(sampleProvider);
sampleProvider.addGBActivitySample(activitySample);
} catch (Exception e) {
LOG.error("Failed to add step data to database", e);
}
}
public void addTotalFitnessData(int steps, int calories, int distance) {
LOG.debug("FITNESS total steps: " + steps);
LOG.debug("FITNESS total calories: " + calories); // TODO: May actually be kilocalories
LOG.debug("FITNESS total distance: " + distance + " m");
// TODO: potentially do more with this, maybe through realtime data?
}
public Long addWorkoutTotalsData(Workout.WorkoutTotals.Response packet) {
try (DBHandler db = GBApplication.acquireDB()) {
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
Long deviceId = DBHelper.getDevice(gbDevice, db.getDaoSession()).getId();
// Avoid duplicates
QueryBuilder<HuaweiWorkoutSummarySample> qb = db.getDaoSession().getHuaweiWorkoutSummarySampleDao().queryBuilder().where(
HuaweiWorkoutSummarySampleDao.Properties.UserId.eq(userId),
HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(deviceId),
HuaweiWorkoutSummarySampleDao.Properties.WorkoutNumber.eq(packet.number),
HuaweiWorkoutSummarySampleDao.Properties.StartTimestamp.eq(packet.startTime),
HuaweiWorkoutSummarySampleDao.Properties.EndTimestamp.eq(packet.endTime)
);
List<HuaweiWorkoutSummarySample> results = qb.build().list();
Long workoutId = null;
if (!results.isEmpty())
workoutId = results.get(0).getWorkoutId();
byte[] raw;
if (packet.rawData == null)
raw = null;
else
raw = StringUtils.bytesToHex(packet.rawData).getBytes(StandardCharsets.UTF_8);
HuaweiWorkoutSummarySample summarySample = new HuaweiWorkoutSummarySample(
workoutId,
deviceId,
userId,
packet.number,
packet.status,
packet.startTime,
packet.endTime,
packet.calories,
packet.distance,
packet.stepCount,
packet.totalTime,
packet.duration,
packet.type,
packet.strokes,
packet.avgStrokeRate,
packet.poolLength,
packet.laps,
packet.avgSwolf,
raw
);
db.getDaoSession().getHuaweiWorkoutSummarySampleDao().insertOrReplace(summarySample);
return summarySample.getWorkoutId();
} catch (Exception e) {
LOG.error("Failed to add workout totals data to database", e);
return null;
}
}
public void addWorkoutSampleData(Long workoutId, List<Workout.WorkoutData.Response.Data> dataList) {
if (workoutId == null)
return;
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiWorkoutDataSampleDao dao = db.getDaoSession().getHuaweiWorkoutDataSampleDao();
for (Workout.WorkoutData.Response.Data data : dataList) {
byte[] unknown;
if (data.unknownData == null)
unknown = null;
else
unknown = StringUtils.bytesToHex(data.unknownData).getBytes(StandardCharsets.UTF_8);
HuaweiWorkoutDataSample dataSample = new HuaweiWorkoutDataSample(
workoutId,
data.timestamp,
data.heartRate,
data.speed,
data.stepRate,
data.cadence,
data.stepLength,
data.groundContactTime,
data.impact,
data.swingAngle,
data.foreFootLanding,
data.midFootLanding,
data.backFootLanding,
data.eversionAngle,
data.swolf,
data.strokeRate,
unknown
);
dao.insertOrReplace(dataSample);
}
} catch (Exception e) {
LOG.error("Failed to add workout data to database", e);
}
}
public void addWorkoutPaceData(Long workoutId, List<Workout.WorkoutPace.Response.Block> paceList) {
if (workoutId == null)
return;
try (DBHandler db = GBApplication.acquireDB()) {
HuaweiWorkoutPaceSampleDao dao = db.getDaoSession().getHuaweiWorkoutPaceSampleDao();
for (Workout.WorkoutPace.Response.Block block : paceList) {
HuaweiWorkoutPaceSample paceSample = new HuaweiWorkoutPaceSample(
workoutId,
block.distance,
block.type,
block.pace,
block.correction
);
dao.insertOrReplace(paceSample);
}
} catch (Exception e) {
LOG.error("Failed to add workout pace data to database", e);
}
}
public void setWearLocation() {
try {
SetWearLocationRequest setWearLocationReq = new SetWearLocationRequest(this);
setWearLocationReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to configure Wear Location", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to configure Wear Location", e);
}
}
public void getBatteryLevel() {
try {
GetBatteryLevelRequest batteryLevelReq = new GetBatteryLevelRequest(this);
batteryLevelReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to get battery Level", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to get battery Level", e);
}
}
public void setActivateOnLift() {
try {
SetActivateOnLiftRequest setActivateOnLiftReq = new SetActivateOnLiftRequest(this);
setActivateOnLiftReq.doPerform();
SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceMac);
boolean statusDndLiftWrist = sharedPrefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_LIFT_WRIST, false);
if (statusDndLiftWrist) {
setDnd();
}
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to configure Activate on Rotate", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to configure Activate on Rotate", e);
}
}
public void setNavigateOnRotate() {
try {
SetNavigateOnRotateRequest setNavigateOnRotateReq = new SetNavigateOnRotateRequest(this);
setNavigateOnRotateReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to configure Navigate on Rotate", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to configure Navigate on Rotate", e);
}
}
public void setActivityReminder() {
try {
SetActivityReminderRequest setActivityReminderReq = new SetActivityReminderRequest(this);
setActivityReminderReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to configure Activity reminder", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to configure Activity reminder", e);
}
}
public void setTrusleep() {
try {
SetTruSleepRequest setTruSleepReq = new SetTruSleepRequest(this);
setTruSleepReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to configure truSleep", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to configure truSleep", e);
}
}
public void setDnd() {
try {
SendDndDeleteRequest sendDndDeleteReq = new SendDndDeleteRequest(this);
SendDndAddRequest sendDndAddReq = new SendDndAddRequest(this);
sendDndDeleteReq.nextRequest(sendDndAddReq);
sendDndDeleteReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to set DND", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to set DND", e);
}
}
public void setDndNotWear() {
try {
SetWearMessagePushRequest setWearMessagePushReq = new SetWearMessagePushRequest(this);
setWearMessagePushReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Setting DND not wear failed", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Setting DND not wear failed", e);
}
}
private void setDisconnectNotification() {
try {
SetDisconnectNotification req = new SetDisconnectNotification(this);
req.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to set disconnect notification", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to set disconnect notification", e);
}
}
private void setHeartrateAutomatic() {
try {
SetAutomaticHeartrateRequest req = new SetAutomaticHeartrateRequest(this);
req.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to set automatic heart rate", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to set automatic heart rate", e);
}
}
private void setSpoAutomatic() {
try {
SetAutomaticSpoRequest req = new SetAutomaticSpoRequest(this);
req.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to set automatic SpO", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to set automatic SpO", e);
}
}
public void sendDebugRequest() {
try {
LOG.debug("Send debug request");
DebugRequest req = new DebugRequest(this);
req.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to send debug request", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to send debug request", e);
}
}
public void onStopFindPhone() {
try {
LOG.debug("Send stop find phone request");
StopFindPhoneRequest stopFindPhoneRequest = new StopFindPhoneRequest(this);
stopFindPhoneRequest.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to send stop find phone request", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to send stop find phone request", e);
}
}
public void setLanguageSetting() {
try {
SetLanguageSettingRequest setLocaleReq = new SetLanguageSettingRequest(this);
setLocaleReq.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to set language settings request", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to set language settings request", e);
}
}
public Weather.WeatherIcon openWeatherMapConditionCodeToHuaweiIcon(int conditionCode) {
// More exact first, groups after
switch (conditionCode) {
case 500:
return Weather.WeatherIcon.LIGHT_RAIN;
case 501:
return Weather.WeatherIcon.RAIN;
case 502:
return Weather.WeatherIcon.HEAVY_RAIN;
case 503:
return Weather.WeatherIcon.RAIN_STORM;
case 504:
return Weather.WeatherIcon.SEVERE_RAIN_STORMS;
case 511:
return Weather.WeatherIcon.FREEZING_RAIN;
case 600:
return Weather.WeatherIcon.LIGHT_SNOW;
case 601:
return Weather.WeatherIcon.SNOW;
case 602:
return Weather.WeatherIcon.HEAVY_SNOW;
case 611:
return Weather.WeatherIcon.SLEET;
case 701:
case 741:
return Weather.WeatherIcon.FOG;
case 721:
return Weather.WeatherIcon.HAZY;
case 751:
return Weather.WeatherIcon.SAND;
case 761:
return Weather.WeatherIcon.DUST;
case 800:
return Weather.WeatherIcon.SUNNY;
case 801:
case 802:
return Weather.WeatherIcon.CLOUDY;
case 803:
case 804:
return Weather.WeatherIcon.OVERCAST;
}
if (conditionCode >= 200 && conditionCode < 300)
return Weather.WeatherIcon.THUNDERSTORMS;
if (conditionCode >= 300 && conditionCode < 400)
return Weather.WeatherIcon.LIGHT_RAIN;
if (conditionCode >= 500 && conditionCode < 600)
return Weather.WeatherIcon.RAIN;
if (conditionCode >= 600 && conditionCode < 700)
return Weather.WeatherIcon.SNOW;
return Weather.WeatherIcon.UNKNOWN;
}
public void onSendWeather(WeatherSpec weatherSpec) {
// Initialize weather settings and send weather
if (!getHuaweiCoordinator().supportsWeather()) {
LOG.error("onSendWeather called while weather is not supported.");
return;
}
Weather.Settings weatherSettings = new Weather.Settings();
SendWeatherStartRequest weatherStartRequest = new SendWeatherStartRequest(this, weatherSettings);
Request lastRequest = weatherStartRequest;
if (getHuaweiCoordinator().supportsWeatherUnit()) {
SendWeatherUnitRequest weatherUnitRequest = new SendWeatherUnitRequest(this);
lastRequest.nextRequest(weatherUnitRequest);
lastRequest = weatherUnitRequest;
}
SendWeatherSupportRequest weatherSupportRequest = new SendWeatherSupportRequest(this, weatherSettings);
lastRequest.nextRequest(weatherSupportRequest);
lastRequest = weatherSupportRequest;
if (getHuaweiCoordinator().supportsWeatherExtended()) {
SendWeatherExtendedSupportRequest weatherExtendedSupportRequest = new SendWeatherExtendedSupportRequest(this, weatherSettings);
lastRequest.nextRequest(weatherExtendedSupportRequest);
lastRequest = weatherExtendedSupportRequest;
}
if (getHuaweiCoordinator().supportsWeatherMoonRiseSet()) {
SendWeatherSunMoonSupportRequest weatherSunMoonSupportRequest = new SendWeatherSunMoonSupportRequest(this, weatherSettings);
lastRequest.nextRequest(weatherSunMoonSupportRequest);
lastRequest = weatherSunMoonSupportRequest;
}
// End of initialization and start of actually sending weather
SendWeatherCurrentRequest sendWeatherCurrentRequest = new SendWeatherCurrentRequest(this, weatherSettings, weatherSpec);
lastRequest.nextRequest(sendWeatherCurrentRequest);
lastRequest = sendWeatherCurrentRequest;
SendGpsAndTimeToDeviceRequest sendGpsAndTimeToDeviceRequest = new SendGpsAndTimeToDeviceRequest(this);
lastRequest.nextRequest(sendGpsAndTimeToDeviceRequest);
lastRequest = sendGpsAndTimeToDeviceRequest;
if (getHuaweiCoordinator().supportsWeatherForecasts()) {
SendWeatherForecastRequest sendWeatherForecastRequest = new SendWeatherForecastRequest(this, weatherSettings, weatherSpec);
lastRequest.nextRequest(sendWeatherForecastRequest);
lastRequest = sendWeatherForecastRequest;
}
try {
weatherStartRequest.doPerform();
} catch (IOException e) {
// TODO: Use translatable string
GB.toast(context, "Failed to send weather", Toast.LENGTH_SHORT, GB.ERROR, e);
LOG.error("Failed to send weather", e);
}
}
}