Zepp OS: Decouple implementation from HuamiSupport

This commit is contained in:
José Rebelo
2025-04-26 18:08:28 +01:00
parent e8f1b5611c
commit aa9027e76f
59 changed files with 944 additions and 632 deletions
+3
View File
@@ -5,6 +5,7 @@
<w>adhikary</w>
<w>afanasev</w>
<w>affero</w>
<w>agps</w>
<w>alaoui</w>
<w>alexey</w>
<w>alipay</w>
@@ -23,6 +24,7 @@
<w>bloß</w>
<w>boun</w>
<w>breathwork</w>
<w>btbr</w>
<w>böhler</w>
<w>carsten</w>
<w>chamorro</w>
@@ -110,6 +112,7 @@
<w>morpheuz</w>
<w>mosenkovs</w>
<w>multisport</w>
<w>nack</w>
<w>nephiel</w>
<w>nodomain</w>
<w>nordhøy</w>
@@ -0,0 +1,51 @@
/* Copyright (C) 2025 José Rebelo
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.huami;
import android.content.SharedPreferences;
import org.apache.commons.lang3.StringUtils;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
public class HuamiDevicePrefs extends DevicePrefs {
public HuamiDevicePrefs(final SharedPreferences preferences, final GBDevice gbDevice) {
super(preferences, gbDevice);
}
public byte getLanguageId() {
byte languageCode = 0x02; // english default
String localeString = getString("language", "auto");
if (StringUtils.isBlank(localeString) || localeString.equals("auto")) {
String language = Locale.getDefault().getLanguage();
String country = Locale.getDefault().getCountry();
localeString = language + "_" + country.toUpperCase();
}
final Integer id = HuamiLanguageType.idLookup.get(localeString);
if (id != null) {
languageCode = id.byteValue();
}
return languageCode;
}
}
@@ -117,7 +117,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsCannedMessagesService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsWorldClocksService;
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
@@ -168,7 +167,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Version;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ACTIVATE_DISPLAY_ON_LIFT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALLOW_HIGH_MTU;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BT_CONNECTED_ADVERTISEMENT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISPLAY_ON_LIFT_SENSITIVITY;
@@ -288,7 +286,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
public void notify(Intent intent) {
String s = intent.getAction();
if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(s)) {
handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
handleDeviceInfo(intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
}
}
};
@@ -296,8 +294,8 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
protected BluetoothGattCharacteristic characteristicHRControlPoint;
private BluetoothGattCharacteristic characteristicChunked;
protected BluetoothGattCharacteristic characteristicChunked2021Write;
protected BluetoothGattCharacteristic characteristicChunked2021Read;
private BluetoothGattCharacteristic characteristicChunked2021Write;
private BluetoothGattCharacteristic characteristicChunked2021Read;
private boolean needsAuth;
private volatile boolean telephoneRinging;
@@ -312,17 +310,16 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
protected MediaManager mediaManager;
private boolean heartRateNotifyEnabled;
protected static final int MIN_MTU = 23;
protected int mMTU = MIN_MTU;
private int mMTU = MIN_MTU;
// Keep track of the previous MTU before reconnection, so that we can request it after reconnection
private int previousMtu = -1;
protected int mActivitySampleSize = 4;
protected Huami2021ChunkedEncoder huami2021ChunkedEncoder;
protected Huami2021ChunkedDecoder huami2021ChunkedDecoder;
private Huami2021ChunkedEncoder huami2021ChunkedEncoder;
private Huami2021ChunkedDecoder huami2021ChunkedDecoder;
private final HuamiFetcher fetcher = new HuamiFetcher(this);
protected SleepAsAndroidSender sleepAsAndroidSender;
public HuamiSupport() {
this(LOG);
}
@@ -349,7 +346,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) {
super.setContext(gbDevice, btAdapter, context);
this.mediaManager = new MediaManager(context);
this.sleepAsAndroidSender = new SleepAsAndroidSender(gbDevice);
}
@Override
@@ -454,8 +450,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(calendar, BLETypeConversions.TZ_FLAG_INCLUDE_DST_IN_TZ) };
// 0 = adjust reason bitflags? or DST offset?? , timezone
// byte[] tail = new byte[] { 0x2 }; // reason
byte[] all = BLETypeConversions.join(bytes, tail);
return all;
return BLETypeConversions.join(bytes, tail);
}
/**
@@ -522,7 +517,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
return this;
}
public HuamiSupport enableFurtherNotifications(TransactionBuilder builder, boolean enable) {
public void enableFurtherNotifications(TransactionBuilder builder, boolean enable) {
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUDIO), enable);
@@ -532,8 +527,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
if (characteristicChunked2021Read != null) {
builder.notify(characteristicChunked2021Read, enable);
}
return this;
}
@Override
@@ -541,19 +534,23 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
return true;
}
@Override
public HuamiDevicePrefs getDevicePrefs() {
return new HuamiDevicePrefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()), gbDevice);
}
@Override
public boolean connectFirstTime() {
needsAuth = true;
return connect();
}
private HuamiSupport sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
private void sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
LOG.info("Sending notification to MiBand: (" + repeat + " times)");
NotificationStrategy strategy = getNotificationStrategy();
for (short i = 0; i < repeat; i++) {
strategy.sendDefaultNotification(builder, simpleNotification, extraAction);
}
return this;
}
public NotificationStrategy getNotificationStrategy() {
@@ -575,27 +572,24 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
private static final byte[] startHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 1};
private static final byte[] stopHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 0};
private HuamiSupport requestBatteryInfo(TransactionBuilder builder) {
private void requestBatteryInfo(TransactionBuilder builder) {
LOG.debug("Requesting Battery Info!");
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO);
builder.read(characteristic);
return this;
}
public HuamiSupport requestDeviceInfo(TransactionBuilder builder) {
public void requestDeviceInfo(TransactionBuilder builder) {
LOG.debug("Requesting Device Info!");
deviceInfoProfile.requestDeviceInfo(builder);
return this;
}
/**
* Part of device initialization process. Do not call manually.
*
* @param transaction
* @return
*/
protected HuamiSupport setFitnessGoal(TransactionBuilder transaction) {
protected void setFitnessGoal(TransactionBuilder transaction) {
LOG.info("Attempting to set Fitness Goal...");
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
if (characteristic != null) {
@@ -609,19 +603,17 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
} else {
LOG.info("Unable to set Fitness Goal");
}
return this;
}
/**
* Part of device initialization process. Do not call manually.
*
* @param transaction
* @return
*/
protected HuamiSupport setUserInfo(TransactionBuilder transaction) {
protected void setUserInfo(TransactionBuilder transaction) {
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
if (characteristic == null) {
return this;
return;
}
LOG.info("Attempting to set user info...");
@@ -637,7 +629,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
if (alias == null || weight == 0 || height == 0 || birth_year == 0) {
LOG.warn("Unable to set user info, make sure it is set up");
return this;
return;
}
byte sex = 2; // other
@@ -671,16 +663,14 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
};
transaction.write(characteristic, bytes);
return this;
}
/**
* Part of device initialization process. Do not call manually.
*
* @param builder
* @return
*/
protected HuamiSupport setWearLocation(TransactionBuilder builder) {
protected void setWearLocation(TransactionBuilder builder) {
LOG.info("Attempting to set wear location...");
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
if (characteristic != null) {
@@ -696,7 +686,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
builder.notify(characteristic, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
}
return this;
}
@Override
@@ -724,7 +713,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
}
private HuamiSupport setPassword(final TransactionBuilder builder) {
private void setPassword(final TransactionBuilder builder) {
final boolean passwordEnabled = HuamiCoordinator.getPasswordEnabled(gbDevice.getAddress());
final String password = HuamiCoordinator.getPassword(gbDevice.getAddress());
@@ -732,7 +721,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
if (password == null || password.isEmpty()) {
LOG.warn("Invalid password: {}", password);
return this;
return;
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -746,12 +735,10 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
baos.write(0x00);
} catch (final IOException e) {
LOG.error("Failed to build password command", e);
return this;
return;
}
writeToConfiguration(builder, baos.toByteArray());
return this;
}
/**
@@ -759,7 +746,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
*
* @param builder
*/
protected HuamiSupport setHeartrateSleepSupport(TransactionBuilder builder) {
protected void setHeartrateSleepSupport(TransactionBuilder builder) {
final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(gbDevice.getAddress());
if (characteristicHRControlPoint != null) {
builder.notify(characteristicHRControlPoint, true);
@@ -772,17 +759,15 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
builder.notify(characteristicHRControlPoint, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
}
return this;
}
protected HuamiSupport setHeartrateActivityMonitoring(TransactionBuilder builder) {
protected void setHeartrateActivityMonitoring(TransactionBuilder builder) {
final boolean enableHrActivityMonitoring = HuamiCoordinator.getHeartrateActivityMonitoring(gbDevice.getAddress());
final byte[] cmd = {ENDPOINT_DISPLAY, 0x22, 0x00, (byte) (enableHrActivityMonitoring ? 0x01 : 0x00)};
writeToConfiguration(builder, cmd);
return this;
}
protected HuamiSupport setHeartrateAlert(TransactionBuilder builder) {
protected void setHeartrateAlert(TransactionBuilder builder) {
final boolean enableHrAlert = HuamiCoordinator.getHeartrateAlert(gbDevice.getAddress());
final int hrAlertThreshold = HuamiCoordinator.getHeartrateAlertHighThreshold(gbDevice.getAddress());
@@ -796,25 +781,22 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
writeToConfiguration(builder, cmd);
return this;
}
protected HuamiSupport setHeartrateStressMonitoring(TransactionBuilder builder) {
protected void setHeartrateStressMonitoring(TransactionBuilder builder) {
final boolean enableHrStressMonitoring = HuamiCoordinator.getHeartrateStressMonitoring(gbDevice.getAddress());
LOG.info("Setting heart rate stress monitoring to {}", enableHrStressMonitoring);
final byte[] cmd = new byte[] {(byte) 0xfe, 0x06, 0x00, (byte) (enableHrStressMonitoring ? 0x01 : 0x00)};
writeToConfiguration(builder, cmd);
return this;
}
private HuamiSupport setHeartrateMeasurementInterval(TransactionBuilder builder, int minutes) {
private void setHeartrateMeasurementInterval(TransactionBuilder builder, int minutes) {
if (characteristicHRControlPoint != null) {
builder.notify(characteristicHRControlPoint, true);
LOG.info("Setting heart rate measurement interval to " + minutes + " minutes");
builder.write(characteristicHRControlPoint, new byte[]{HuamiService.COMMAND_SET_PERIODIC_HR_MEASUREMENT_INTERVAL, (byte) minutes});
builder.notify(characteristicHRControlPoint, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
}
return this;
}
private void performDefaultNotification(String task, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
@@ -1459,14 +1441,12 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
}
public HuamiSupport sendReboot(TransactionBuilder builder) {
public void sendReboot(TransactionBuilder builder) {
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_FIRMWARE), new byte[] { HuamiService.COMMAND_FIRMWARE_REBOOT});
return this;
}
public HuamiSupport sendFactoryReset(TransactionBuilder builder) {
public void sendFactoryReset(TransactionBuilder builder) {
writeToConfiguration(builder, HuamiService.COMMAND_FACTORY_RESET);
return this;
}
@Override
@@ -2369,7 +2349,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
MiBand2SampleProvider provider = new MiBand2SampleProvider(gbDevice, session);
MiBandActivitySample sample = createActivitySample(device, user, ts, provider);
sample.setHeartRate(getHeartrateBpm());
sleepAsAndroidSender.onHrChanged(sample.getHeartRate(), 0);
// sample.setSteps(getSteps());
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
@@ -2462,7 +2441,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
if (versionCmd.fwVersion == null) {
versionCmd.fwVersion = info.getSoftwareRevision();
}
if (versionCmd.fwVersion != null && versionCmd.fwVersion.length() > 0 && versionCmd.fwVersion.charAt(0) == 'V') {
if (versionCmd.fwVersion != null && !versionCmd.fwVersion.isEmpty() && versionCmd.fwVersion.charAt(0) == 'V') {
versionCmd.fwVersion = versionCmd.fwVersion.substring(1);
}
handleGBDeviceEvent(versionCmd);
@@ -2475,28 +2454,27 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
}
private HuamiSupport sendCalendarEvents(TransactionBuilder builder) {
private void sendCalendarEvents(TransactionBuilder builder) {
if (characteristicChunked == null) { // all except Mi Band 2
sendCalendarEventsAsAlarms(builder);
} else {
sendCalendarEventsAsReminders(builder);
}
return this;
}
/**
* Fetch the events from the android device calendars and set the alarms on the miband.
*
* @param builder
*/
private HuamiSupport sendCalendarEventsAsAlarms(TransactionBuilder builder) {
private void sendCalendarEventsAsAlarms(TransactionBuilder builder) {
DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
int maxAlarms = coordinator.getAlarmSlotCount(gbDevice);
int availableSlots = Math.min(prefs.getInt(PREF_RESERVER_ALARMS_CALENDAR, 0), maxAlarms);
if (availableSlots <= 0) {
return this;
return;
}
CalendarManager upcomingEvents = new CalendarManager(getContext(), getDevice().getAddress());
@@ -2519,13 +2497,12 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
iteration++;
}
return this;
}
private HuamiSupport sendCalendarEventsAsReminders(TransactionBuilder builder) {
private void sendCalendarEventsAsReminders(TransactionBuilder builder) {
boolean syncCalendar = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_SYNC_CALENDAR, false);
if (!syncCalendar) {
return this;
return;
}
final DeviceCoordinator coordinator = gbDevice.getDeviceCoordinator();
@@ -2586,8 +2563,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
buf.put(new byte[6 + 6 + 1 + 1]); // default value is 0
writeToChunked(builder, 2, buf.array());
}
return this;
}
@Override
@@ -2763,7 +2738,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
}
private HuamiSupport setVibrationPattern(final TransactionBuilder builder, final String preferenceKey) {
private void setVibrationPattern(final TransactionBuilder builder, final String preferenceKey) {
// The preference key has one of the 3 prefixes
final String notificationTypeName = preferenceKey.replace(PREF_HUAMI_VIBRATION_COUNT_PREFIX, "")
.replace(PREF_HUAMI_VIBRATION_PROFILE_PREFIX, "")
@@ -2779,8 +2754,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
);
setVibrationPattern(builder, notificationType, isTry, vibrationProfile);
return this;
}
/**
@@ -3079,7 +3052,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
}
protected HuamiSupport setDateDisplay(TransactionBuilder builder) {
protected void setDateDisplay(TransactionBuilder builder) {
DateTimeDisplay dateTimeDisplay = HuamiCoordinator.getDateDisplay(getContext(), gbDevice.getAddress());
LOG.info("Setting date display to " + dateTimeDisplay);
switch (dateTimeDisplay) {
@@ -3090,13 +3063,12 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
writeToConfiguration(builder,HuamiService.DATEFORMAT_DATE_TIME);
break;
}
return this;
}
protected HuamiSupport setDateFormat(TransactionBuilder builder) {
protected void setDateFormat(TransactionBuilder builder) {
String dateFormat = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("dateformat", "MM/dd/yyyy");
if (dateFormat == null) {
return this;
return;
}
switch (dateFormat) {
case "MM/dd/yyyy":
@@ -3110,10 +3082,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
LOG.warn("unsupported date format " + dateFormat);
}
return this;
}
private HuamiSupport setTimeFormat(TransactionBuilder builder) {
private void setTimeFormat(TransactionBuilder builder) {
String timeFormat = GBApplication.getDevicePrefs(gbDevice).getTimeFormat();
LOG.info("Setting time format to " + timeFormat);
@@ -3122,7 +3093,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
} else {
writeToConfiguration(builder,HuamiService.DATEFORMAT_TIME_12_HOURS);
}
return this;
}
protected HuamiSupport setGoalNotification(TransactionBuilder builder) {
@@ -3136,7 +3106,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
return this;
}
protected HuamiSupport setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
protected void setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
ActivateDisplayOnLift displayOnLift = HuamiCoordinator.getActivateDisplayOnLiftWrist(getContext(), gbDevice.getAddress());
LOG.info("Setting activate display on lift wrist to " + displayOnLift);
@@ -3167,10 +3137,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
default:
LOG.warn("Unknown display on lift mode {}", displayOnLift);
}
return this;
}
protected HuamiSupport setActivateDisplayOnLiftWristSensitivity(TransactionBuilder builder) {
protected void setActivateDisplayOnLiftWristSensitivity(TransactionBuilder builder) {
final ActivateDisplayOnLiftSensitivity sensitivity = HuamiCoordinator.getDisplayOnLiftSensitivity(gbDevice.getAddress());
LOG.info("Setting activate display on lift wrist sensitivity to " + sensitivity);
@@ -3183,8 +3152,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
writeToConfiguration(builder, HuamiService.COMMAND_DISPLAY_ON_LIFT_WRIST_SPEED_NORMAL);
break;
}
return this;
}
protected HuamiSupport setDisplayItems(TransactionBuilder builder) {
@@ -3217,7 +3184,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
return this;
}
protected HuamiSupport setDisplayItemsOld(TransactionBuilder builder, boolean isShortcuts, int defaultSettings, Map<String, Integer> keyPosMap) {
protected void setDisplayItemsOld(TransactionBuilder builder, boolean isShortcuts, int defaultSettings, Map<String, Integer> keyPosMap) {
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
String pages;
List<String> enabledList;
@@ -3285,7 +3252,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
writeToConfiguration(builder, command);
return this;
}
protected HuamiSupport setDisplayItemsNew(TransactionBuilder builder, boolean isShortcuts, boolean forceWatchface, int defaultSettings) {
@@ -3346,11 +3312,10 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
return this;
}
protected HuamiSupport setShortcuts(TransactionBuilder builder) {
return this;
protected void setShortcuts(TransactionBuilder builder) {
}
protected HuamiSupport setWorkoutActivityTypes(final TransactionBuilder builder) {
protected void setWorkoutActivityTypes(final TransactionBuilder builder) {
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
final List<String> defaultActivityTypes = Arrays.asList(HuamiWorkoutScreenActivityType.Freestyle.name().toLowerCase(Locale.ROOT));
@@ -3392,16 +3357,14 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
writeToChunked(builder, 9, command.array());
return this;
}
protected HuamiSupport setBeepSounds(TransactionBuilder builder) {
protected void setBeepSounds(TransactionBuilder builder) {
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
Set<String> sounds = prefs.getStringSet(PREF_SOUNDS, new HashSet<>(Arrays.asList(getContext().getResources().getStringArray(R.array.pref_amazfitneo_sounds_default))));
LOG.info("Setting sounds to " + (sounds == null ? "none" : sounds));
if (sounds != null) {
final String[] soundOrder = new String[]{"button", "calls", "alarm", "notifications", "inactivity_warning", "sms", "email", "goal"};
byte[] command = new byte[]{0x3c, 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0};
@@ -3414,11 +3377,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
writeToChunked(builder, 2, command);
}
return this;
}
protected HuamiSupport setRotateWristToSwitchInfo(TransactionBuilder builder) {
protected void setRotateWristToSwitchInfo(TransactionBuilder builder) {
boolean enable = HuamiCoordinator.getRotateWristToSwitchInfo(gbDevice.getAddress());
LOG.info("Setting rotate wrist to cycle info to " + enable);
if (enable) {
@@ -3426,15 +3387,13 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
} else {
writeToConfiguration(builder, HuamiService.COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO);
}
return this;
}
protected HuamiSupport setDisplayCaller(TransactionBuilder builder) {
protected void setDisplayCaller(TransactionBuilder builder) {
writeToConfiguration(builder, HuamiService.COMMAND_ENABLE_DISPLAY_CALLER);
return this;
}
protected HuamiSupport setDoNotDisturb(TransactionBuilder builder) {
protected void setDoNotDisturb(TransactionBuilder builder) {
DoNotDisturb doNotDisturb = HuamiCoordinator.getDoNotDisturb(gbDevice.getAddress());
boolean doNotDisturbLiftWrist = HuamiCoordinator.getDoNotDisturbLiftWrist(gbDevice.getAddress());
LOG.info("Setting do not disturb to {}, wake on lift wrist {}", doNotDisturb, doNotDisturbLiftWrist);
@@ -3472,11 +3431,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
writeToConfiguration(builder, data);
}
return this;
}
protected HuamiSupport setNightMode(TransactionBuilder builder) {
protected void setNightMode(TransactionBuilder builder) {
String nightMode = MiBand3Coordinator.getNightMode(gbDevice.getAddress());
LOG.info("Setting night mode to " + nightMode);
@@ -3508,11 +3465,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
LOG.error("Invalid night mode: " + nightMode);
break;
}
return this;
}
protected HuamiSupport setInactivityWarnings(TransactionBuilder builder) {
protected void setInactivityWarnings(TransactionBuilder builder) {
boolean enable = HuamiCoordinator.getInactivityWarnings(gbDevice.getAddress());
LOG.info("Setting inactivity warnings to " + enable);
@@ -3562,13 +3517,11 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
} else {
writeToConfiguration(builder, HuamiService.COMMAND_DISABLE_INACTIVITY_WARNINGS);
}
return this;
}
protected HuamiSupport setHourlyChime(TransactionBuilder builder) {
protected void setHourlyChime(TransactionBuilder builder) {
if (!supportsHourlyChime())
return this;
return;
boolean enable = HuamiCoordinator.getHourlyChime(gbDevice.getAddress());
LOG.info("Setting hourly chime to " + enable);
@@ -3593,15 +3546,13 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
} else {
writeToConfiguration(builder, HuamiService.COMMAND_DISABLE_HOURLY_CHIME);
}
return this;
}
public boolean supportsHourlyChime() {
return false;
}
protected HuamiSupport setDisconnectNotification(TransactionBuilder builder) {
private void setDisconnectNotification(TransactionBuilder builder) {
DisconnectNotificationSetting disconnectNotificationSetting = HuamiCoordinator.getDisconnectNotificationSetting(getContext(), gbDevice.getAddress());
LOG.info("Setting disconnect notification to " + disconnectNotificationSetting);
@@ -3629,10 +3580,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
writeToConfiguration(builder, cmd);
}
return this;
}
private HuamiSupport setDistanceUnit(TransactionBuilder builder) {
private void setDistanceUnit(TransactionBuilder builder) {
MiBandConst.DistanceUnit unit = HuamiCoordinator.getDistanceUnit();
LOG.info("Setting distance unit to " + unit);
if (unit == MiBandConst.DistanceUnit.METRIC) {
@@ -3640,10 +3590,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
} else {
writeToConfiguration(builder, HuamiService.COMMAND_DISTANCE_UNIT_IMPERIAL);
}
return this;
}
protected HuamiSupport setBandScreenUnlock(TransactionBuilder builder) {
protected void setBandScreenUnlock(TransactionBuilder builder) {
boolean enable = MiBand3Coordinator.getBandScreenUnlock(gbDevice.getAddress());
LOG.info("Setting band screen unlock to " + enable);
@@ -3652,11 +3601,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
} else {
writeToConfiguration(builder, MiBand3Service.COMMAND_DISABLE_BAND_SCREEN_UNLOCK);
}
return this;
}
protected HuamiSupport setLanguage(TransactionBuilder builder) {
protected void setLanguage(TransactionBuilder builder) {
String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("language", "auto");
if (localeString == null || localeString.equals("auto")) {
String language = Locale.getDefault().getLanguage();
@@ -3704,39 +3651,18 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
});
}
return this;
}
/*
Some newer devices seem to support setting the language by id again instead of a locale string
Amazfit Bip U and GTS 2 mini tested so far
*/
protected HuamiSupport setLanguageByIdNew(TransactionBuilder builder) {
final byte[] command = new byte[]{0x06, 0x3b, 0x00, getLanguageId(), 0x03};
protected void setLanguageByIdNew(TransactionBuilder builder) {
final byte[] command = new byte[]{0x06, 0x3b, 0x00, getDevicePrefs().getLanguageId(), 0x03};
writeToConfiguration(builder, command);
return this;
}
protected byte getLanguageId() {
byte language_code = 0x02; // english default
String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("language", "auto");
if (localeString == null || localeString.equals("auto")) {
String language = Locale.getDefault().getLanguage();
String country = Locale.getDefault().getCountry();
localeString = language + "_" + country.toUpperCase();
}
Integer id = HuamiLanguageType.idLookup.get(localeString);
if (id != null) {
language_code = id.byteValue();
}
return language_code;
}
protected HuamiSupport setExposeHRThirdParty(TransactionBuilder builder) {
protected void setExposeHRThirdParty(TransactionBuilder builder) {
boolean enable = HuamiCoordinator.getExposeHRThirdParty(gbDevice.getAddress());
LOG.info("Setting exposure of HR to third party apps to: " + enable);
@@ -3745,11 +3671,9 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
} else {
writeToConfiguration(builder, HuamiService.COMMAND_DISABLE_HR_CONNECTION);
}
return this;
}
protected HuamiSupport setBtConnectedAdvertising(TransactionBuilder builder) {
protected void setBtConnectedAdvertising(TransactionBuilder builder) {
boolean enable = HuamiCoordinator.getBtConnectedAdvertising(gbDevice.getAddress());
LOG.info("Setting connected advertisement to: " + enable);
@@ -3758,8 +3682,6 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
} else {
writeToConfiguration(builder, HuamiService.COMMAND_DISABLE_BT_CONNECTED_ADVERTISEMENT);
}
return this;
}
protected void writeToChunked(TransactionBuilder builder, int type, byte[] data) {
@@ -3835,18 +3757,16 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
}
protected HuamiSupport requestGPSVersion(TransactionBuilder builder) {
protected void requestGPSVersion(TransactionBuilder builder) {
LOG.info("Requesting GPS version");
writeToConfiguration(builder, HuamiService.COMMAND_REQUEST_GPS_VERSION);
return this;
}
protected HuamiSupport requestAlarms(TransactionBuilder builder) {
protected void requestAlarms(TransactionBuilder builder) {
LOG.info("Requesting alarms");
//FIXME: on older devices only the first one works, and on newer only the last is sufficient
writeToConfiguration(builder, HuamiService.COMMAND_REQUEST_ALARMS);
writeToConfiguration(builder, HuamiService.COMMAND_REQUEST_ALARMS_WITH_TIMES);
return this;
}
@Override
@@ -3949,7 +3869,7 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
protected void setMtu(final int mtu) {
if (mtu > MIN_MTU && !allowHighMtu()) {
if (mtu > MIN_MTU && !getDevicePrefs().allowHighMtu()) {
LOG.warn("High MTU is not allowed, ignoring");
return;
}
@@ -3965,22 +3885,10 @@ public abstract class HuamiSupport extends AbstractBTLEDeviceSupport
}
}
protected boolean allowHighMtu() {
return getDevicePrefs().getBoolean(PREF_ALLOW_HIGH_MTU, true);
}
public int getActivitySampleSize() {
return mActivitySampleSize;
}
public TimeUnit getFetchOperationsTimeUnit() {
// This is configurable because using seconds was causing issues on Amazfit GTR 3
// However, using minutes can cause issues while fetching workouts shorter than 1 minute
final Prefs devicePrefs = getDevicePrefs();
final boolean truncate = devicePrefs.getBoolean("huami_truncate_fetch_operation_timestamps", true);
return truncate ? TimeUnit.MINUTES : TimeUnit.SECONDS;
}
public boolean force2021Protocol() {
return GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean("force_new_protocol", false);
}
@@ -35,9 +35,8 @@ public class AmazfitBand5Support extends MiBand5Support {
}
@Override
protected AmazfitBand5Support setShortcuts(TransactionBuilder builder) {
protected void setShortcuts(TransactionBuilder builder) {
setDisplayItemsNew(builder, true, true, R.array.pref_amazfitband5_shortcuts_default);
return this;
}
@Override
@@ -65,13 +65,12 @@ public class AmazfitBipSupport extends HuamiSupport {
}
@Override
protected AmazfitBipSupport setShortcuts(TransactionBuilder builder) {
protected void setShortcuts(TransactionBuilder builder) {
Map<String, Integer> keyPosMap = new LinkedHashMap<>();
keyPosMap.put("alipay", 1);
keyPosMap.put("weather", 2);
setDisplayItemsOld(builder, true, R.array.pref_bip_shortcuts_default, keyPosMap);
return this;
}
@Override
@@ -99,9 +99,8 @@ public class AmazfitBipSSupport extends AmazfitBipSupport {
}
@Override
protected AmazfitBipSSupport setShortcuts(TransactionBuilder builder) {
protected void setShortcuts(TransactionBuilder builder) {
setDisplayItemsNew(builder, true, true, R.array.pref_bips_display_items_default);
return this;
}
private boolean isDTH(Version version) {
@@ -24,7 +24,6 @@ import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbipu.AmazfitBipUFWHelper;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbips.AmazfitBipSSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
@@ -32,8 +31,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.upd
public class AmazfitBipUSupport extends AmazfitBipSSupport {
@Override
protected HuamiSupport setLanguage(TransactionBuilder builder) {
return setLanguageByIdNew(builder);
protected void setLanguage(TransactionBuilder builder) {
setLanguageByIdNew(builder);
}
@Override
@@ -28,15 +28,14 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitgts2.AmazfitGTS2MiniFWHelper;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
public class AmazfitGTS2MiniSupport extends AmazfitGTS2Support {
private static final Logger LOG = LoggerFactory.getLogger(AmazfitGTS2MiniSupport.class);
@Override
protected HuamiSupport setLanguage(TransactionBuilder builder) {
return setLanguageByIdNew(builder);
protected void setLanguage(TransactionBuilder builder) {
setLanguageByIdNew(builder);
}
@Override
@@ -61,10 +61,9 @@ public class AmazfitNeoSupport extends MiBand5Support {
}
@Override
protected AmazfitNeoSupport setFitnessGoal(TransactionBuilder builder) {
protected void setFitnessGoal(TransactionBuilder builder) {
LOG.info("Attempting to set Fitness Goal...");
setNeoFitnessGoal(builder);
return this;
}
@Override
@@ -87,19 +86,18 @@ public class AmazfitNeoSupport extends MiBand5Support {
}
@Override
protected AmazfitNeoSupport requestAlarms(TransactionBuilder builder) {
return this; //Neo always returns response array with '03' in it which marks alarms unused on connect
protected void requestAlarms(TransactionBuilder builder) {
//Neo always returns response array with '03' in it which marks alarms unused on connect
}
@Override
public boolean supportsHourlyChime() { return true; }
@Override
protected AmazfitNeoSupport setHeartrateSleepSupport(TransactionBuilder builder) {
protected void setHeartrateSleepSupport(TransactionBuilder builder) {
final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(gbDevice.getAddress());
LOG.info("Setting Amazfit Neo heartrate sleep support to " + enableHrSleepSupport);
writeToConfiguration(builder, new byte[] {0x06, 0x3c, 0x00, (byte) (enableHrSleepSupport ? 1 : 0 )});
return this;
}
@Override
@@ -40,9 +40,8 @@ public class AmazfitXSupport extends MiBand5Support {
}
@Override
protected AmazfitXSupport setShortcuts(TransactionBuilder builder) {
protected void setShortcuts(TransactionBuilder builder) {
setDisplayItemsNew(builder, true, true, R.array.pref_amazfitband5_shortcuts_default);
return this;
}
@Override
@@ -42,9 +42,8 @@ public class MiBand5Support extends MiBand4Support {
}
@Override
protected MiBand5Support setShortcuts(TransactionBuilder builder) {
protected void setShortcuts(TransactionBuilder builder) {
setDisplayItemsNew(builder, true, true, R.array.pref_miband5_shortcuts_default);
return this;
}
@Override
@@ -56,9 +56,8 @@ public class MiBand6Support extends MiBand5Support {
}
@Override
protected MiBand6Support setShortcuts(TransactionBuilder builder) {
protected void setShortcuts(TransactionBuilder builder) {
setDisplayItemsNew(builder, true, true, R.array.pref_miband6_shortcuts_default);
return this;
}
@Override
@@ -100,9 +100,6 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
byte[] srcBytes = authKey.trim().getBytes();
if (authKey.length() == 34 && authKey.startsWith("0x")) {
srcBytes = GB.hexStringToByteArray(authKey.substring(2));
} else if (authKey.length() == 32 && huamiSupport instanceof ZeppOsSupport) {
// All Zepp OS devices require a hex key
srcBytes = GB.hexStringToByteArray(authKey);
}
System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length, 16));
}
@@ -23,11 +23,13 @@ import androidx.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Consumer;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiDevicePrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public abstract class AbstractZeppOsService {
@@ -67,7 +69,7 @@ public abstract class AbstractZeppOsService {
return false;
}
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
// Do nothing by default
// TODO implement a "quick initialize" that runs for the same firmware + Gb versions, since
// we will already know the capabilities
@@ -86,8 +88,8 @@ public abstract class AbstractZeppOsService {
return (ZeppOsCoordinator) coordinator;
}
protected Prefs getDevicePrefs() {
return new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getSupport().getDevice().getAddress()));
protected HuamiDevicePrefs getDevicePrefs() {
return getSupport().getDevicePrefs();
}
protected void write(final String taskName, final byte b) {
@@ -98,11 +100,11 @@ public abstract class AbstractZeppOsService {
this.mSupport.writeToChunked2021(taskName, getEndpoint(), data, isEncrypted());
}
protected void write(final TransactionBuilder builder, final byte b) {
protected void write(final ZeppOsTransactionBuilder builder, final byte b) {
this.write(builder, new byte[]{b});
}
protected void write(final TransactionBuilder builder, final byte[] data) {
protected void write(final ZeppOsTransactionBuilder builder, final byte[] data) {
this.mSupport.writeToChunked2021(builder, getEndpoint(), data, isEncrypted());
}
@@ -114,6 +116,16 @@ public abstract class AbstractZeppOsService {
return getSupport().getContext();
}
protected ZeppOsTransactionBuilder createTransactionBuilder(final String taskName) {
return mSupport.createZeppOsTransactionBuilder(taskName);
}
protected void withTransactionBuilder(final String taskName, final Consumer<ZeppOsTransactionBuilder> consumer) {
final ZeppOsTransactionBuilder builder = createTransactionBuilder(taskName);
consumer.accept(builder);
builder.queue(mSupport);
}
@Nullable
protected static Boolean booleanFromByte(final byte b) {
switch (b) {
@@ -0,0 +1,67 @@
/* Copyright (C) 2025 José Rebelo
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.huami.zeppos;
import android.content.Context;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
public class ZeppOsBtleTransactionBuilder implements ZeppOsTransactionBuilder {
private final AbstractBTLEDeviceSupport mSupport;
private final TransactionBuilder mBuilder;
public ZeppOsBtleTransactionBuilder(final AbstractBTLEDeviceSupport mSupport, final String taskName) {
this.mSupport = mSupport;
this.mBuilder = new TransactionBuilder(taskName);
}
public ZeppOsBtleTransactionBuilder(final AbstractBTLEDeviceSupport mSupport, final TransactionBuilder builder) {
this.mSupport = mSupport;
this.mBuilder = builder;
}
@Override
public void setProgress(final String text, final boolean ongoing, final int percentage, final Context context) {
mBuilder.add(new SetProgressAction(text, ongoing, percentage, context));
}
@Override
public void setDeviceState(final GBDevice device, final GBDevice.State deviceState, final Context context) {
mBuilder.add(new SetDeviceStateAction(device, deviceState, context));
}
@Override
public void notify(final UUID characteristic, final boolean enable) {
mBuilder.notify(mSupport.getCharacteristic(characteristic), enable);
}
@Override
public void write(final UUID characteristic, final byte[] arr) {
mBuilder.write(mSupport.getCharacteristic(characteristic), arr);
}
@Override
public void queue(final ZeppOsSupport support) {
mBuilder.queue(support.getQueue());
}
}
@@ -21,8 +21,6 @@ import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversio
import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.fromUint8;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions.mapTimeZone;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.HEART_RATE_ALL_DAY_MONITORING;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.LANGUAGE;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.LANGUAGE_FOLLOW_PHONE;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.SLEEP_HIGH_ACCURACY_MONITORING;
import android.bluetooth.BluetoothAdapter;
@@ -63,19 +61,18 @@ import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsMapsInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsMusicInstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.Huami2021Service;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
@@ -95,14 +92,20 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021ChunkedDecoder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021ChunkedEncoder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.Huami2021Handler;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiDevicePrefs;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFetcher;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsFirmwareUpdateOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsAgpsUpdateOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsGpxRouteUploadOperation;
@@ -150,13 +153,47 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.RealtimeSamplesAggregator;
public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferService.DownloadCallback {
public class ZeppOsSupport extends AbstractBTLEDeviceSupport
implements Huami2021Handler, HuamiFetcher.HuamiFetchSupport, ZeppOsFileTransferService.DownloadCallback {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsSupport.class);
// Keep track of whether the rawSensor is enabled
private boolean rawSensor = false;
private ScheduledExecutorService rawSensorScheduler;
private RealtimeSamplesAggregator realtimeSamplesAggregator;
private final DeviceInfoProfile<ZeppOsSupport> deviceInfoProfile;
private final IntentListener mListener = intent -> {
String s = intent.getAction();
if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(s)) {
final DeviceInfo info = intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO);
LOG.debug("Device info: {}", info);
if (info == null) {
return;
}
final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
versionCmd.hwVersion = info.getHardwareRevision();
versionCmd.fwVersion = info.getFirmwareRevision();
if (versionCmd.fwVersion == null) {
versionCmd.fwVersion = info.getSoftwareRevision();
}
if (versionCmd.fwVersion != null && !versionCmd.fwVersion.isEmpty() && versionCmd.fwVersion.charAt(0) == 'V') {
versionCmd.fwVersion = versionCmd.fwVersion.substring(1);
}
handleGBDeviceEvent(versionCmd);
}
};
protected static final int MIN_MTU = 23;
private int mMTU = MIN_MTU;
// Keep track of the previous MTU before reconnection, so that we can request it after reconnection
private int previousMtu = -1;
private Huami2021ChunkedEncoder huami2021ChunkedEncoder;
private Huami2021ChunkedDecoder huami2021ChunkedDecoder;
private final HuamiFetcher fetcher = new HuamiFetcher(this);
private SleepAsAndroidSender sleepAsAndroidSender;
// Services
private final ZeppOsServicesService servicesService = new ZeppOsServicesService(this);
@@ -241,40 +278,36 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}};
public ZeppOsSupport() {
this(LOG);
}
super(LOG);
public ZeppOsSupport(final Logger logger) {
super(logger);
addSupportedService(GattService.UUID_SERVICE_HEART_RATE);
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
addSupportedService(HuamiService.UUID_SERVICE_FIRMWARE_SERVICE);
deviceInfoProfile = new DeviceInfoProfile<>(this);
deviceInfoProfile.addListener(mListener);
addSupportedProfile(deviceInfoProfile);
}
@Override
public void setContext(final GBDevice gbDevice, final BluetoothAdapter btAdapter, final Context context) {
super.setContext(gbDevice, btAdapter, context);
heartRateService.setSleepAsAndroidSender(sleepAsAndroidSender);
realtimeSamplesAggregator = new RealtimeSamplesAggregator(getContext(), getDevice());
final RealtimeSamplesAggregator realtimeSamplesAggregator = new RealtimeSamplesAggregator(getContext(), getDevice());
heartRateService.setRealtimeSamplesAggregator(realtimeSamplesAggregator);
stepsService.setRealtimeSamplesAggregator(realtimeSamplesAggregator);
}
@Override
protected byte getAuthFlags() {
return 0x00;
public HuamiDevicePrefs getDevicePrefs() {
return new HuamiDevicePrefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()), gbDevice);
}
@Override
public byte getCryptFlags() {
return (byte) 0x80;
}
/**
* Do not reset the gatt callback implicitly, as that would interrupt operations.
* See <a href="https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2912">#2912</a> for more
* information.
*/
@Override
public boolean getImplicitCallbackModify() {
return false;
public boolean useAutoConnect() {
return true;
}
@Override
@@ -295,12 +328,12 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
setMtu(MIN_MTU);
}
characteristicChunked2021Read = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ);
final BluetoothGattCharacteristic characteristicChunked2021Read = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ);
if (characteristicChunked2021Read != null && huami2021ChunkedDecoder == null) {
huami2021ChunkedDecoder = new Huami2021ChunkedDecoder(this, force2021Protocol());
huami2021ChunkedDecoder = new Huami2021ChunkedDecoder(this, true);
}
characteristicChunked2021Write = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE);
final BluetoothGattCharacteristic characteristicChunked2021Write = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE);
if (characteristicChunked2021Write != null && huami2021ChunkedEncoder == null) {
huami2021ChunkedEncoder = new Huami2021ChunkedEncoder(getMTU());
}
@@ -312,8 +345,9 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
builder.notify(characteristicChunked2021Read, true);
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
authenticationService.startAuthentication(builder);
authenticationService.startAuthentication(new ZeppOsBtleTransactionBuilder(this, builder));
return builder;
}
@@ -344,8 +378,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
findDeviceService.onFindDevice(start);
}
@Override
protected void sendFindDeviceCommand(boolean start) {
private void sendFindDeviceCommand(boolean start) {
findDeviceService.sendFindDeviceCommand(start);
}
@@ -355,7 +388,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
voiceMemosService.requestList();
}
super.onFetchRecordedData(dataTypes);
fetcher.onFetchRecordedData(dataTypes);
}
@Override
@@ -380,11 +413,11 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
minuteInterval = Math.max(0, minuteInterval);
}
final TransactionBuilder builder = createTransactionBuilder(String.format(Locale.ROOT, "set heart rate interval to: %d min", minuteInterval));
final ZeppOsTransactionBuilder builder = createZeppOsTransactionBuilder(String.format(Locale.ROOT, "set heart rate interval to: %d min", minuteInterval));
configService.newSetter()
.setByte(HEART_RATE_ALL_DAY_MONITORING, (byte) minuteInterval)
.write(builder);
builder.queue(getQueue());
builder.queue(this);
}
@Override
@@ -408,8 +441,8 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
@Override
protected void queueAlarm(final Alarm alarm, final TransactionBuilder builder) {
alarmsService.sendAlarm(alarm, builder);
public void onSetAlarms(final ArrayList<? extends Alarm> alarms) {
alarmsService.onSetAlarms(alarms);
}
@Override
@@ -424,14 +457,9 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
@Override
public void onSetReminders(final ArrayList<? extends Reminder> reminders) {
final TransactionBuilder builder;
try {
builder = performInitialized("onSetReminders");
remindersService.sendReminders(builder, reminders);
builder.queue(getQueue());
} catch (final IOException e) {
LOG.error("Unable to send reminders to device", e);
}
final ZeppOsTransactionBuilder builder = createZeppOsTransactionBuilder("onSetReminders");
remindersService.sendReminders(builder, reminders);
builder.queue(this);
}
@Override
@@ -471,8 +499,13 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
@Override
protected void sendMusicStateToDevice(final MusicSpec musicSpec, final MusicStateSpec musicStateSpec) {
musicService.sendMusicState(musicSpec, musicStateSpec);
public void onSetMusicState(final MusicStateSpec stateSpec) {
musicService.onSetMusicState(stateSpec);
}
@Override
public void onSetMusicInfo(final MusicSpec musicSpec) {
musicService.onSetMusicInfo(musicSpec);
}
@Override
@@ -480,19 +513,6 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
stepsService.onEnableRealtimeSteps(enable);
}
@Override
public UpdateFirmwareOperation createUpdateFirmwareOperation(final Uri uri) {
throw new UnsupportedOperationException("this method should not be used");
}
/**
* FIXME: Visibility hack for easier refactoring.
*/
@Override
public void setMtu(final int mtu) {
super.setMtu(mtu);
}
@Override
public void onInstallApp(final Uri uri) {
final ZeppOsAgpsInstallHandler agpsHandler = new ZeppOsAgpsInstallHandler(uri, getContext());
@@ -652,14 +672,15 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
@Override
protected ZeppOsSupport setHeartrateSleepSupport(final TransactionBuilder builder) {
public void onEnableHeartRateSleepSupport(boolean enable) {
final ZeppOsTransactionBuilder builder = createZeppOsTransactionBuilder("enable heart rate sleep support: " + enable);
final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(gbDevice.getAddress());
configService.newSetter()
.setBoolean(SLEEP_HIGH_ACCURACY_MONITORING, enableHrSleepSupport)
.write(builder);
return this;
builder.queue(this);
}
@Override
@@ -676,33 +697,23 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
@Override
public void onSetTime() {
if (!GBApplication.getPrefs().syncTime()) {
return;
if (GBApplication.getPrefs().syncTime()) {
final ZeppOsTransactionBuilder builder = createZeppOsTransactionBuilder("set date and time");
setCurrentTime(builder);
builder.queue(this);
}
try {
final TransactionBuilder builder = performInitialized("set date and time");
setCurrentTime(builder);
builder.queue(getQueue());
CalendarReceiver.forceSync(getDevice());
} catch (IOException ex) {
LOG.error("Unable to set time on Huami device", ex);
}
CalendarReceiver.forceSync(getDevice());
}
@Override
public void setCurrentTime(TransactionBuilder builder) {
if (!GBApplication.getPrefs().syncTime()) {
return;
}
private void setCurrentTime(ZeppOsTransactionBuilder builder) {
// It seems that the format sent to the Current Time characteristic changed in newer devices
// to kind-of match the GATT spec, but it doesn't quite respect it?
// - 11 bytes get sent instead of 10 (extra byte at the end for the offset in quarter-hours?)
// - Day of week starts at 0
// Otherwise, the command gets rejected with an "Out of Range" error and init fails.
final Calendar timestamp = createCalendar();
final Calendar timestamp = BLETypeConversions.createCalendar();
final byte[] year = fromUint16(timestamp.get(Calendar.YEAR));
final byte[] cmd = {
@@ -719,20 +730,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
mapTimeZone(timestamp, BLETypeConversions.TZ_FLAG_INCLUDE_DST_IN_TZ), // TODO: Confirm this
};
builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), cmd);
}
@Override
public HuamiSupport enableNotifications(final TransactionBuilder builder, final boolean enable) {
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ), enable);
return this;
}
@Override
public ZeppOsSupport enableFurtherNotifications(final TransactionBuilder builder,
final boolean enable) {
// Nothing to do here, they are already enabled from enableNotifications
return this;
builder.write(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME, cmd);
}
@Override
@@ -779,7 +777,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
case SleepAsAndroidAction.SET_SUSPENDED:
boolean suspended = extras.getBoolean("SUSPENDED", false);
setRawSensor(!suspended);
enableRealtimeSamplesTimer(!suspended);
//FIXME is this really needed? enableRealtimeSamplesTimer(!suspended);
sleepAsAndroidSender.pauseTracking(suspended);
break;
// Received when the app changes the batch size for the movement data
@@ -866,86 +864,39 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
setRawSensor(enable);
if (enable) {
rawSensorScheduler = startRawSensors();
}
else {
} else {
stopRawSensors();
}
}
@Override
protected ZeppOsSupport setLanguage(final TransactionBuilder builder) {
final String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress())
.getString("language", "auto");
LOG.info("Setting device language to {}", localeString);
configService.newSetter()
.setByte(LANGUAGE, getLanguageId())
.setBoolean(LANGUAGE_FOLLOW_PHONE, localeString.equals("auto"))
.write(builder);
return this;
}
@Override
protected void writeToChunked(final TransactionBuilder builder,
final int type,
final byte[] data) {
LOG.warn("writeToChunked is not supported");
}
@Override
protected void writeToChunkedOld(final TransactionBuilder builder, final int type, final byte[] data) {
LOG.warn("writeToChunkedOld is not supported");
}
@Override
public void writeToConfiguration(final TransactionBuilder builder, final byte[] data) {
LOG.warn("writeToConfiguration is not supported");
}
@Override
protected ZeppOsSupport requestGPSVersion(final TransactionBuilder builder) {
LOG.warn("Request GPS version not implemented");
return this;
}
public void requestDisplayItems(final TransactionBuilder builder) {
public void requestDisplayItems(final ZeppOsTransactionBuilder builder) {
displayItemsService.requestItems(builder, ZeppOsDisplayItemsService.DISPLAY_ITEMS_MENU);
}
public void requestApps(final TransactionBuilder builder) {
public void requestApps(final ZeppOsTransactionBuilder builder) {
appsService.requestApps(builder);
}
public void requestWatchfaces(final TransactionBuilder builder) {
public void requestWatchfaces(final ZeppOsTransactionBuilder builder) {
watchfaceService.requestWatchfaces(builder);
watchfaceService.requestCurrentWatchface(builder);
}
@Override
public void phase2Initialize(final TransactionBuilder builder) {
LOG.info("2021 phase2Initialize...");
if (allowHighMtu()) {
builder.requestMtu(247);
public void onAuthenticationSuccess() {
LOG.info("ZeppOS phase 2 initialize...");
final TransactionBuilder bleBuilder = new TransactionBuilder("phase 2 initialize");
final var builder = new ZeppOsBtleTransactionBuilder(this, bleBuilder);
builder.setDeviceState(getDevice(), GBDevice.State.INITIALIZING, getContext());
if (getDevicePrefs().allowHighMtu()) {
bleBuilder.requestMtu(247);
}
if (GBApplication.getPrefs().syncTime()) {
setCurrentTime(builder);
}
requestDeviceInfo(builder);
final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences()
.withPreference(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STATUS, null)
.withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_ADDRESS, null)
.withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_USERNAME, null)
.withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_STATUS, null);
evaluateGBDeviceEvent(evt);
}
@Override
public void phase3Initialize(final TransactionBuilder builder) {
LOG.info("2021 phase3Initialize...");
deviceInfoProfile.requestDeviceInfo(bleBuilder);
// Make sure that performInitialized is not called accidentally in here
// (eg. by creating a new TransactionBuilder).
@@ -956,12 +907,8 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
// initializeServices below
mSupportedServices.clear();
servicesService.requestServices(builder);
}
@Override
@Deprecated
public HuamiFWHelper createFWHelper(final Uri uri, final Context context) throws IOException {
throw new UnsupportedOperationException("This function should not be used for Zepp OS devices");
builder.queue(this);
}
public void addSupportedService(final short endpoint, final boolean encrypted) {
@@ -972,7 +919,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
LOG.info("2021 initializeServices...");
try {
final TransactionBuilder builder = createTransactionBuilder("initialize services");
final ZeppOsTransactionBuilder builder = createZeppOsTransactionBuilder("initialize services");
// At this point we got the service list from phase 3, so we know which
// services are supported, and whether they are encrypted or not
@@ -986,12 +933,29 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
}
builder.queue(getQueue());
builder.setDeviceState(getDevice(), GBDevice.State.INITIALIZED, getContext());
builder.queue(this);
} catch (Exception e) {
LOG.error("failed initializing device", e);
}
}
@Override
public void setActivityNotifications(final boolean control, final boolean data) {
final ZeppOsTransactionBuilder builder = createZeppOsTransactionBuilder("set activity notifications: " + control + " " + data);
builder.notify(HuamiService.UUID_CHARACTERISTIC_5_ACTIVITY_CONTROL, control);
builder.notify(HuamiService.UUID_CHARACTERISTIC_5_ACTIVITY_DATA, data);
builder.queue(this);
}
@Override
public void writeActivityControl(final String name, final byte[] value) {
final ZeppOsTransactionBuilder builder = createZeppOsTransactionBuilder(name);
builder.write(HuamiService.UUID_CHARACTERISTIC_5_ACTIVITY_CONTROL, value);
builder.queue(this);
}
@Nullable
public AbstractZeppOsService getService(final short endpoint) {
return mServiceMap.get(endpoint);
@@ -1002,18 +966,12 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
return 8;
}
@Override
public boolean force2021Protocol() {
return true;
}
@Override
public ZeppOsCoordinator getCoordinator() {
return (ZeppOsCoordinator) gbDevice.getDeviceCoordinator();
}
@Override
protected void setRawSensor(final boolean enable) {
private void setRawSensor(final boolean enable) {
LOG.info("Set raw sensor to {}", enable);
rawSensor = enable;
@@ -1033,8 +991,7 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
}
}
@Override
protected void handleRawSensorData(final byte[] value) {
private void handleRawSensorData(final byte[] value) {
// The g values seem to vary between -4100 and 4100, so we scale them
final float scaleFactor = 4100f;
final float gravity = -9.81f;
@@ -1086,8 +1043,22 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
@Override
public boolean onCharacteristicChanged(final BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic) {
if (super.onCharacteristicChanged(gatt, characteristic)) {
// handled upstream
return true;
}
final UUID characteristicUUID = characteristic.getUuid();
if (HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3_SEND.equals(characteristicUUID)) {
if (HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ.equals(characteristicUUID)) {
handleChunked(characteristic.getValue());
return true;
} else if (HuamiService.UUID_CHARACTERISTIC_5_ACTIVITY_DATA.equals(characteristicUUID)) {
fetcher.onActivityData(characteristic.getValue());
return true;
} else if (HuamiService.UUID_CHARACTERISTIC_5_ACTIVITY_CONTROL.equals(characteristicUUID)) {
fetcher.onActivityControl(characteristic.getValue());
return true;
} else if (HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3_SEND.equals(characteristicUUID)) {
fileTransferService.onCharacteristicChanged(characteristic);
return true;
} else if (HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3_RECEIVE.equals(characteristicUUID)) {
@@ -1096,9 +1067,68 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
} else if (GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
heartRateService.handleHeartRate(characteristic.getValue());
return true;
} else if (HuamiService.UUID_CHARACTERISTIC_RAW_SENSOR_DATA.equals(characteristicUUID)) {
handleRawSensorData(characteristic.getValue());
return true;
} else {
LOG.warn("Unhandled characteristic changed: {}", characteristicUUID);
logMessageContent(characteristic.getValue());
}
return super.onCharacteristicChanged(gatt, characteristic);
return false;
}
public int getMTU() {
return mMTU;
}
public void setMtu(final int mtu) {
if (mtu > MIN_MTU && !getDevicePrefs().allowHighMtu()) {
LOG.warn("High MTU is not allowed, ignoring");
return;
}
if (mtu < MIN_MTU) {
LOG.error("Device announced unreasonable low MTU of {}, ignoring", mtu);
return;
}
this.mMTU = mtu;
if (huami2021ChunkedEncoder != null) {
huami2021ChunkedEncoder.setMTU(mtu);
}
}
private void handleChunked(final byte[] value) {
switch (value[0]) {
case 0x03:
if (huami2021ChunkedDecoder != null) {
final boolean needsAck = huami2021ChunkedDecoder.decode(value);
if (needsAck) {
sendChunkedAck();
}
} else {
LOG.warn("Got chunked payload, but decoder is null");
}
return;
case 0x04:
final byte handle = value[2];
final byte count = value[4];
LOG.info("Got chunked ack, handle={}, count={}", handle, count);
// TODO: We should probably update the handle and count on the encoder
return;
default:
LOG.warn("Unhandled chunked payload of type {}", value[0]);
}
}
public void sendChunkedAck() {
final byte handle = huami2021ChunkedDecoder.getLastHandle();
final byte count = huami2021ChunkedDecoder.getLastCount();
final ZeppOsTransactionBuilder builder = createZeppOsTransactionBuilder("send chunked ack");
builder.write(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ, new byte[] {0x04, 0x00, handle, 0x01, count});
builder.queue(this);
}
@Override
@@ -1163,4 +1193,27 @@ public class ZeppOsSupport extends HuamiSupport implements ZeppOsFileTransferSer
private byte bool(final boolean b) {
return (byte) (b ? 1 : 0);
}
public void writeToChunked2021(final ZeppOsTransactionBuilder builder, final short type, final byte data, final boolean encrypt) {
writeToChunked2021(builder, type, new byte[]{data}, encrypt);
}
public void writeToChunked2021(final ZeppOsTransactionBuilder builder, final short type, final byte[] data, final boolean encrypt) {
huami2021ChunkedEncoder.write(chunk -> builder.write(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_WRITE, chunk), type, data, true, encrypt);
}
public void writeToChunked2021(final String taskName, final short type, final byte data, final boolean encrypt) {
writeToChunked2021(taskName, type, new byte[]{data}, encrypt);
}
public void writeToChunked2021(final String taskName, final short type, final byte[] data, final boolean encrypt) {
final ZeppOsTransactionBuilder builder = createZeppOsTransactionBuilder(taskName);
writeToChunked2021(builder, type, data, encrypt);
builder.queue(this);
}
public ZeppOsTransactionBuilder createZeppOsTransactionBuilder(final String taskName) {
// FIXME this needs to be moved upstream
return new ZeppOsBtleTransactionBuilder(this, taskName);
}
}
@@ -0,0 +1,35 @@
/* Copyright (C) 2025 José Rebelo
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.huami.zeppos;
import android.content.Context;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public interface ZeppOsTransactionBuilder {
void setProgress(final String text, final boolean ongoing, final int percentage, final Context context);
void setDeviceState(final GBDevice device, final GBDevice.State deviceState, final Context context);
void notify(final UUID characteristic, final boolean enable);
void write(final UUID characteristic, final byte[] arr);
void queue(final ZeppOsSupport support);
}
@@ -0,0 +1,92 @@
/* Copyright (C) 2025 José Rebelo
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.huami.zeppos.operations;
import android.content.Context;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
/**
* This is a simplified version of the AbstractBTLEOperation, in order to allow for simpler
* Zepp OS btrfcomm support without a major refactor. It should not be used for new operations.
*/
@Deprecated
public abstract class AbstractZeppOsOperation<T extends ZeppOsSupport> {
private final T mSupport;
protected OperationStatus operationStatus = OperationStatus.INITIAL;
private String name;
protected AbstractZeppOsOperation(T support) {
mSupport = support;
}
public final void perform() throws IOException {
operationStatus = OperationStatus.RUNNING;
doPerform();
}
protected abstract void doPerform() throws IOException;
protected void operationFinished() throws IOException {
}
protected Context getContext() {
return mSupport.getContext();
}
protected GBDevice getDevice() {
return mSupport.getDevice();
}
protected void setName(String name) {
this.name = name;
}
public String getName() {
if (name != null) {
return name;
}
String busyTask = getDevice().getBusyTask();
if (busyTask != null) {
return busyTask;
}
return getClass().getSimpleName();
}
protected void unsetBusy() {
if (getDevice().isBusy()) {
getDevice().unsetBusyTask();
getDevice().sendDeviceUpdateIntent(getContext());
}
}
public boolean isOperationRunning() {
return operationStatus == OperationStatus.RUNNING;
}
public boolean isOperationFinished() {
return operationStatus == OperationStatus.FINISHED;
}
public T getSupport() {
return mSupport;
}
}
@@ -22,10 +22,8 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsAgpsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
@@ -40,7 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
* 4. After successful ack from 3, update is finished. Trigger an AGPS config request from {@link ZeppOsConfigService}
* to reload the AGPS update and expiration timestamps.
*/
public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<ZeppOsSupport>
public class ZeppOsAgpsUpdateOperation extends AbstractZeppOsOperation<ZeppOsSupport>
implements ZeppOsFileTransferService.UploadCallback, ZeppOsAgpsService.Callback {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsUpdateOperation.class);
@@ -120,9 +118,9 @@ public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<ZeppOsSuppo
public void onAgpsUpdateFinishResponse(final boolean success) {
if (success) {
try {
final TransactionBuilder builder = performInitialized("request agps config");
final ZeppOsTransactionBuilder builder = getSupport().createZeppOsTransactionBuilder("request agps config");
configService.requestConfig(builder, ZeppOsConfigService.ConfigGroup.AGPS);
builder.queue(getQueue());
builder.queue(getSupport());
} catch (final Exception e) {
LOG.error("Failed to request agps config", e);
}
@@ -133,9 +131,9 @@ public class ZeppOsAgpsUpdateOperation extends AbstractBTLEOperation<ZeppOsSuppo
private void updateProgress(final int progressPercent) {
try {
final TransactionBuilder builder = performInitialized("send agps update progress");
builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext()));
builder.queue(getQueue());
final ZeppOsTransactionBuilder builder = getSupport().createZeppOsTransactionBuilder("send agps update progress");
builder.setProgress(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext());
builder.queue(getSupport());
} catch (final Exception e) {
LOG.error("Failed to update progress notification", e);
}
@@ -42,6 +42,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressActi
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiFirmwareType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.update.UpdateFirmwareOperation2020;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.AbstractMiBandOperation;
import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -177,9 +178,9 @@ public class ZeppOsFirmwareUpdateOperation extends AbstractMiBandOperation<ZeppO
break;
case UpdateFirmwareOperation2020.COMMAND_FINALIZE_UPDATE: {
if (fwHelper.getFirmwareType() == HuamiFirmwareType.FIRMWARE) {
TransactionBuilder builder = performInitialized("reboot");
getSupport().sendReboot(builder);
builder.queue(getQueue());
ZeppOsTransactionBuilder builder = getSupport().createZeppOsTransactionBuilder("reboot");
builder.write(HuamiService.UUID_CHARACTERISTIC_FIRMWARE, new byte[]{HuamiService.COMMAND_FIRMWARE_REBOOT});
builder.queue(getSupport());
} else {
GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
done();
@@ -225,24 +226,16 @@ public class ZeppOsFirmwareUpdateOperation extends AbstractMiBandOperation<ZeppO
if (ArrayUtils.startsWith(value, new byte[]{HuamiService.RESPONSE, UpdateFirmwareOperation2020.COMMAND_FINALIZE_UPDATE, HuamiService.SUCCESS})) {
if (fwHelper.getFirmwareType() == HuamiFirmwareType.APP) {
// After an app is installed, request the display items from the band (new app will be at the end)
try {
TransactionBuilder builder = performInitialized("request display items and apps");
getSupport().requestDisplayItems(builder);
getSupport().requestApps(builder);
builder.queue(getQueue());
} catch (final IOException e) {
LOG.error("Failed to request display items after app install", e);
}
ZeppOsTransactionBuilder builder = getSupport().createZeppOsTransactionBuilder("request display items and apps");
getSupport().requestDisplayItems(builder);
getSupport().requestApps(builder);
builder.queue(getSupport());
} else if (fwHelper.getFirmwareType() == HuamiFirmwareType.WATCHFACE) {
// After a watchface is installed, request the watchfaces from the band (new watchface will be at the end)
try {
TransactionBuilder builder = performInitialized("request watchfaces and apps");
getSupport().requestWatchfaces(builder);
getSupport().requestApps(builder);
builder.queue(getQueue());
} catch (final IOException e) {
LOG.error("Failed to request watchfaces after watchface install", e);
}
ZeppOsTransactionBuilder builder = getSupport().createZeppOsTransactionBuilder("request watchfaces and apps");
getSupport().requestWatchfaces(builder);
getSupport().requestApps(builder);
builder.queue(getSupport());
}
}
}
@@ -22,15 +22,13 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ZeppOsGpxRouteUploadOperation extends AbstractBTLEOperation<ZeppOsSupport>
public class ZeppOsGpxRouteUploadOperation extends AbstractZeppOsOperation<ZeppOsSupport>
implements ZeppOsFileTransferService.UploadCallback {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsGpxRouteUploadOperation.class);
@@ -91,9 +89,9 @@ public class ZeppOsGpxRouteUploadOperation extends AbstractBTLEOperation<ZeppOsS
private void updateProgress(final int progressPercent) {
try {
final TransactionBuilder builder = performInitialized("send gpx route upload progress");
builder.add(new SetProgressAction(getContext().getString(R.string.gpx_route_upload_in_progress), true, progressPercent, getContext()));
builder.queue(getQueue());
final ZeppOsTransactionBuilder builder = getSupport().createZeppOsTransactionBuilder("send gpx route upload progress");
builder.setProgress(getContext().getString(R.string.gpx_route_upload_in_progress), true, progressPercent, getContext());
builder.queue(getSupport());
} catch (final Exception e) {
LOG.error("Failed to update progress notification", e);
}
@@ -23,16 +23,14 @@ import java.io.IOException;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetProgressAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.audio.AudioInfo;
public class ZeppOsMusicUploadOperation extends AbstractBTLEOperation<ZeppOsSupport>
public class ZeppOsMusicUploadOperation extends AbstractZeppOsOperation<ZeppOsSupport>
implements ZeppOsFileTransferService.UploadCallback {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsMusicUploadOperation.class);
@@ -99,9 +97,9 @@ public class ZeppOsMusicUploadOperation extends AbstractBTLEOperation<ZeppOsSupp
private void updateProgress(final int progressPercent) {
try {
final TransactionBuilder builder = performInitialized("send music upload progress");
builder.add(new SetProgressAction(getContext().getString(R.string.music_upload_in_progress), true, progressPercent, getContext()));
builder.queue(getQueue());
final ZeppOsTransactionBuilder builder = getSupport().createZeppOsTransactionBuilder("send music upload progress");
builder.setProgress(getContext().getString(R.string.music_upload_in_progress), true, progressPercent, getContext());
builder.queue(getSupport());
} catch (final Exception e) {
LOG.error("Failed to update progress notification", e);
}
@@ -27,6 +27,7 @@ import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
public class ZeppOsAgpsService extends AbstractZeppOsService {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAgpsService.class);
@@ -81,6 +82,11 @@ public class ZeppOsAgpsService extends AbstractZeppOsService {
}
}
@Override
public void initialize(final ZeppOsTransactionBuilder builder) {
this.mCallback = null;
}
public void startUpload(final int size) {
final ByteBuffer buf = ByteBuffer.allocate(5)
.order(ByteOrder.LITTLE_ENDIAN)
@@ -17,25 +17,29 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
import android.content.Intent;
import android.widget.Toast;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ZeppOsAlarmsService extends AbstractZeppOsService {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAlarmsService.class);
@@ -99,27 +103,45 @@ public class ZeppOsAlarmsService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestAlarms(builder);
}
private void requestAlarms() {
try {
final TransactionBuilder builder = new TransactionBuilder("request alarms");
requestAlarms(builder);
builder.queue(getSupport().getQueue());
} catch (final Exception e) {
LOG.error("Failed to request alarms", e);
}
withTransactionBuilder("request alarms", this::requestAlarms);
}
public void requestAlarms(final TransactionBuilder builder) {
public void requestAlarms(final ZeppOsTransactionBuilder builder) {
LOG.info("Requesting alarms");
write(builder, new byte[]{CMD_REQUEST});
}
public void sendAlarm(final Alarm alarm, final TransactionBuilder builder) {
public void onSetAlarms(final ArrayList<? extends Alarm> alarms) {
final int maxAlarms = getCoordinator().getAlarmSlotCount(getSupport().getDevice());
final ZeppOsTransactionBuilder builder = createTransactionBuilder("set alarms");
boolean anyAlarmEnabled = false;
for (final Alarm alarm : alarms) {
if (alarm.getPosition() >= maxAlarms) {
if (alarm.getEnabled()) {
GB.toast(getContext(), "Only " + maxAlarms + " alarms are currently supported.", Toast.LENGTH_LONG, GB.WARN);
}
break;
}
anyAlarmEnabled |= alarm.getEnabled();
sendAlarm(alarm, builder);
}
builder.queue(getSupport());
if (anyAlarmEnabled) {
GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO);
} else {
GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
}
}
public void sendAlarm(final Alarm alarm, final ZeppOsTransactionBuilder builder) {
final DeviceCoordinator coordinator = getSupport().getDevice().getDeviceCoordinator();
final Calendar calendar = AlarmUtils.toCalendar(alarm);
@@ -28,9 +28,9 @@ import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
public class ZeppOsAppsService extends AbstractZeppOsService {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsAppsService.class);
@@ -78,7 +78,7 @@ public class ZeppOsAppsService extends AbstractZeppOsService {
}
}
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestApps(builder);
}
@@ -92,7 +92,7 @@ public class ZeppOsAppsService extends AbstractZeppOsService {
private void handleAppsPayload(final byte[] payload) {
if (payload[1] != CMD_INCOMING) {
LOG.warn("Unexpected non-incoming payload ({})", String.format("0x%02x", payload[1]));
LOG.warn("Unexpected non-incoming apps payload ({})", String.format("0x%02x", payload[1]));
return;
}
@@ -115,9 +115,10 @@ public class ZeppOsAppsService extends AbstractZeppOsService {
}
}
/** @noinspection SwitchStatementWithTooFewBranches*/
private void handleScreenshotPayload(final byte[] payload) {
if (payload[1] != CMD_INCOMING) {
LOG.warn("Unexpected non-incoming payload ({})", String.format("0x%02x", payload[1]));
LOG.warn("Unexpected non-incoming screenshot payload ({})", String.format("0x%02x", payload[1]));
return;
}
@@ -164,7 +165,7 @@ public class ZeppOsAppsService extends AbstractZeppOsService {
// TODO broadcast something to update app manager
}
public void requestApps(final TransactionBuilder builder) {
public void requestApps(final ZeppOsTransactionBuilder builder) {
LOG.info("Request apps");
final ByteBuffer buf = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN);
@@ -48,9 +48,9 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePref
import nodomain.freeyourgadget.gadgetbridge.devices.huami.zeppos.ZeppOsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -140,6 +140,25 @@ public class ZeppOsAssistantService extends AbstractZeppOsService {
}
}
@Override
public void dispose() {
handler.removeCallbacksAndMessages(null);
opusDecoder = null;
if (audioTrack != null) {
audioTrack.release();
audioTrack = null;
}
if (rawVoiceOutputStream != null) {
try {
rawVoiceOutputStream.close();
} catch (final IOException e) {
LOG.error("Failed to close raw voice output stream", e);
}
rawVoiceOutputStream = null;
}
voiceBuffer.clear();
}
@Override
public boolean onSendConfiguration(final String config, final Prefs prefs) {
switch (config) {
@@ -170,13 +189,29 @@ public class ZeppOsAssistantService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
handler.removeCallbacksAndMessages(null);
opusDecoder = null;
if (audioTrack != null) {
audioTrack.release();
audioTrack = null;
}
if (rawVoiceOutputStream != null) {
try {
rawVoiceOutputStream.close();
} catch (final IOException e) {
LOG.error("Failed to close raw voice output stream", e);
}
rawVoiceOutputStream = null;
}
voiceBuffer.clear();
if (ZeppOsCoordinator.experimentalFeatures(getSupport().getDevice())) {
requestCapabilities(builder);
}
}
public void requestCapabilities(final TransactionBuilder builder) {
public void requestCapabilities(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
}
@@ -285,13 +320,13 @@ public class ZeppOsAssistantService extends AbstractZeppOsService {
public void sendVoiceReply(final List<byte[]> voiceFrames) {
try {
final TransactionBuilder builder = getSupport().performInitialized("send voice reply");
final ZeppOsTransactionBuilder builder = createTransactionBuilder("send voice reply");
for (final byte[] voiceFrame : voiceFrames) {
// TODO encode
}
builder.queue(getSupport().getQueue());
builder.queue(getSupport());
} catch (final Exception e) {
LOG.error("Failed to send voice reply", e);
}
@@ -29,10 +29,9 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.CryptoUtils;
import nodomain.freeyourgadget.gadgetbridge.util.ECDH_B163;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -122,14 +121,7 @@ public class ZeppOsAuthenticationService extends AbstractZeppOsService {
LOG.debug("Auth Success");
try {
// Authenticated, now initialize phase 2
final TransactionBuilder builder = getSupport().createTransactionBuilder("Authenticated, now initialize phase 2");
builder.add(new SetDeviceStateAction(getSupport().getDevice(), GBDevice.State.INITIALIZING, getContext()));
builder.setCallback(null); // remove init operation as the callback
getSupport().phase2Initialize(builder);
getSupport().phase3Initialize(builder);
getSupport().setInitialized(builder);
getSupport().performImmediately(builder);
getSupport().onAuthenticationSuccess();
} catch (Exception e) {
LOG.error("failed initializing device", e);
}
@@ -139,7 +131,7 @@ public class ZeppOsAuthenticationService extends AbstractZeppOsService {
LOG.warn("Got unknown auth byte {}", String.format("0x%02x", payload[0]));
}
public void startAuthentication(final TransactionBuilder builder) {
public void startAuthentication(final ZeppOsTransactionBuilder builder) {
new Random().nextBytes(privateEC);
final byte[] pub = ECDH_B163.ecdh_generate_public(privateEC);
@@ -21,10 +21,10 @@ import static org.apache.commons.lang3.ArrayUtils.subarray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
public class ZeppOsBatteryService extends AbstractZeppOsService {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsBatteryService.class);
@@ -59,11 +59,11 @@ public class ZeppOsBatteryService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestBatteryInfo(builder);
}
public void requestBatteryInfo(final TransactionBuilder builder) {
public void requestBatteryInfo(final ZeppOsTransactionBuilder builder) {
LOG.debug("Requesting Battery Info");
write(builder, CMD_BATTERY_REQUEST);
@@ -26,9 +26,9 @@ import java.nio.charset.StandardCharsets;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class ZeppOsCalendarService extends AbstractZeppOsService {
@@ -59,11 +59,11 @@ public class ZeppOsCalendarService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestCapabilities(builder);
}
public void requestCapabilities(final TransactionBuilder builder) {
public void requestCapabilities(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
}
@@ -35,9 +35,9 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificati
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class ZeppOsCannedMessagesService extends AbstractZeppOsService {
@@ -99,7 +99,7 @@ public class ZeppOsCannedMessagesService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestCannedMessages(builder);
}
@@ -109,7 +109,7 @@ public class ZeppOsCannedMessagesService extends AbstractZeppOsService {
return;
}
final TransactionBuilder builder = new TransactionBuilder("set canned messages");
final ZeppOsTransactionBuilder builder = createTransactionBuilder("set canned messages");
for (int i = 0; i < 16; i++) {
LOG.debug("Deleting canned message {}", i);
@@ -135,10 +135,10 @@ public class ZeppOsCannedMessagesService extends AbstractZeppOsService {
buf.put(cannedMessage.getBytes(StandardCharsets.UTF_8));
write(builder, buf.array());
}
builder.queue(getSupport().getQueue());
builder.queue(getSupport());
}
public void requestCannedMessages(final TransactionBuilder builder) {
public void requestCannedMessages(final ZeppOsTransactionBuilder builder) {
LOG.info("Requesting canned messages");
write(builder, new byte[]{CMD_REQUEST});
@@ -24,6 +24,8 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_END;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_NIGHT_MODE_START;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.LANGUAGE;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService.ConfigArg.LANGUAGE_FOLLOW_PHONE;
import android.text.TextUtils;
import android.widget.Toast;
@@ -75,11 +77,11 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsMenuType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -133,7 +135,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
requestAllConfigs(builder);
}
@@ -149,24 +151,22 @@ public class ZeppOsConfigService extends AbstractZeppOsService {
case ActivityUser.PREF_USER_GOAL_WEIGHT_KG:
case ActivityUser.PREF_USER_GOAL_STANDING_TIME_HOURS:
case ActivityUser.PREF_USER_GOAL_FAT_BURN_TIME_MINUTES: {
final TransactionBuilder builder = new TransactionBuilder("set fitness goal");
setFitnessGoal(builder);
builder.queue(getSupport().getQueue());
withTransactionBuilder("set fitness goal", this::setFitnessGoal);
return true;
}
// Measurement system is global
case SettingsActivity.PREF_MEASUREMENT_SYSTEM: {
final TransactionBuilder builder = new TransactionBuilder("set measurement system");
setMeasurementSystem(builder);
builder.queue(getSupport().getQueue());
withTransactionBuilder("set measurement system", this::setMeasurementSystem);
return true;
}
// Password needs sanity checks
case PasswordCapabilityImpl.PREF_PASSWORD:
case PasswordCapabilityImpl.PREF_PASSWORD_ENABLED: {
final TransactionBuilder builder = new TransactionBuilder("set " + prefKey);
setPassword(builder);
builder.queue(getSupport().getQueue());
withTransactionBuilder("set " + prefKey, this::setPassword);
return true;
}
case PREF_LANGUAGE: {
withTransactionBuilder("set language", this::setLanguage);
return true;
}
}
@@ -179,9 +179,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService {
if (setConfig(prefs, prefKey, configSetter)) {
try {
// If the ConfigSetter was able to set the config, just write it and return
final TransactionBuilder builder = new TransactionBuilder("send config " + prefKey);
configSetter.write(builder);
builder.queue(getSupport().getQueue());
withTransactionBuilder("send config " + prefKey, configSetter::write);
} catch (final Exception e) {
GB.toast("Error setting configuration", Toast.LENGTH_LONG, GB.ERROR, e);
}
@@ -192,7 +190,18 @@ public class ZeppOsConfigService extends AbstractZeppOsService {
return false;
}
private void setPassword(final TransactionBuilder builder) {
private void setLanguage(final ZeppOsTransactionBuilder builder) {
final String localeString = getDevicePrefs().getString("language", "auto");
LOG.info("Setting device language to {}", localeString);
newSetter()
.setByte(LANGUAGE, getDevicePrefs().getLanguageId())
.setBoolean(LANGUAGE_FOLLOW_PHONE, localeString.equals("auto"))
.write(builder);
}
private void setPassword(final ZeppOsTransactionBuilder builder) {
final boolean passwordEnabled = HuamiCoordinator.getPasswordEnabled(getSupport().getDevice().getAddress());
final String password = HuamiCoordinator.getPassword(getSupport().getDevice().getAddress());
@@ -209,7 +218,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService {
.write(builder);
}
protected void setFitnessGoal(final TransactionBuilder builder) {
protected void setFitnessGoal(final ZeppOsTransactionBuilder builder) {
final int goalSteps = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, ActivityUser.defaultUserStepsGoal);
final int goalCalories = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_CALORIES_BURNT, ActivityUser.defaultUserCaloriesBurntGoal);
final int goalSleep = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_SLEEP_DURATION, ActivityUser.defaultUserSleepDurationGoal);
@@ -228,7 +237,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService {
.write(builder);
}
private void setMeasurementSystem(final TransactionBuilder builder) {
private void setMeasurementSystem(final ZeppOsTransactionBuilder builder) {
final String measurementSystem = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, "metric");
LOG.info("Setting measurement system to {}", measurementSystem);
@@ -312,11 +321,11 @@ public class ZeppOsConfigService extends AbstractZeppOsService {
getSupport().evaluateGBDeviceEvent(eventUpdatePreferences);
if (getSupport().getDevice().isInitialized()) {
if (prefs.containsKey(PREF_LANGUAGE) && prefs.get(PREF_LANGUAGE).equals(PREF_LANGUAGE_AUTO)) {
if (prefs.containsKey(PREF_LANGUAGE) && PREF_LANGUAGE_AUTO.equals(prefs.get(PREF_LANGUAGE))) {
// Band is reporting automatic language, we need to send the actual language
getSupport().onSendConfiguration(PREF_LANGUAGE);
}
if (prefs.containsKey(PREF_TIMEFORMAT) && prefs.get(PREF_TIMEFORMAT).equals(PREF_TIMEFORMAT_AUTO)) {
if (prefs.containsKey(PREF_TIMEFORMAT) && PREF_TIMEFORMAT_AUTO.equals(prefs.get(PREF_TIMEFORMAT))) {
// Band is reporting automatic time format, we need to send the actual time format
getSupport().onSendConfiguration(PREF_TIMEFORMAT);
}
@@ -329,17 +338,17 @@ public class ZeppOsConfigService extends AbstractZeppOsService {
}
}
public void requestAllConfigs(final TransactionBuilder builder) {
public void requestAllConfigs(final ZeppOsTransactionBuilder builder) {
for (final ConfigGroup configGroup : ConfigGroup.values()) {
requestConfig(builder, configGroup);
}
}
public void requestConfig(final TransactionBuilder builder, final ConfigGroup config) {
public void requestConfig(final ZeppOsTransactionBuilder builder, final ConfigGroup config) {
requestConfig(builder, config, true, ZeppOsConfigService.ConfigArg.getAllArgsForConfigGroup(config));
}
public void requestConfig(final TransactionBuilder builder,
public void requestConfig(final ZeppOsTransactionBuilder builder,
final ConfigGroup config,
final boolean includeConstraints,
final List<ZeppOsConfigService.ConfigArg> args) {
@@ -906,7 +915,7 @@ public class ZeppOsConfigService extends AbstractZeppOsService {
return baos.toByteArray();
}
public void write(final TransactionBuilder builder) {
public void write(final ZeppOsTransactionBuilder builder) {
// Write one command per config group
for (final ConfigGroup configGroup : arguments.keySet()) {
ZeppOsConfigService.this.write(builder, encode(configGroup));
@@ -20,9 +20,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
public class ZeppOsConnectionService extends AbstractZeppOsService {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsConnectionService.class);
@@ -61,7 +61,7 @@ public class ZeppOsConnectionService extends AbstractZeppOsService {
LOG.warn("Unexpected connection payload byte {}", String.format("0x%02x", payload[0]));
}
public void requestMTU(final TransactionBuilder builder) {
public void requestMTU(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_MTU_REQUEST);
}
}
@@ -26,9 +26,9 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class ZeppOsContactsService extends AbstractZeppOsService {
@@ -41,7 +41,6 @@ public class ZeppOsContactsService extends AbstractZeppOsService {
public static final byte CMD_SET_LIST = 0x07;
public static final byte CMD_SET_LIST_ACK = 0x08;
private int version = 0;
private int maxContacts = 0;
public static final String PREF_CONTACTS_SLOT_COUNT = "zepp_os_contacts_slot_count";
@@ -59,7 +58,7 @@ public class ZeppOsContactsService extends AbstractZeppOsService {
public void handlePayload(final byte[] payload) {
switch (payload[0]) {
case CMD_CAPABILITIES_RESPONSE:
version = payload[1];
final int version = payload[1];
if (version != 1) {
LOG.warn("Unsupported contacts service version {}", version);
return;
@@ -77,11 +76,11 @@ public class ZeppOsContactsService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestCapabilities(builder);
}
public void requestCapabilities(final TransactionBuilder builder) {
public void requestCapabilities(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
}
@@ -40,10 +40,10 @@ import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsMenuType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -123,7 +123,7 @@ public class ZeppOsDisplayItemsService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestItems(builder, DISPLAY_ITEMS_MENU);
requestItems(builder, DISPLAY_ITEMS_SHORTCUTS);
if (getCoordinator().supportsControlCenter()) {
@@ -131,7 +131,7 @@ public class ZeppOsDisplayItemsService extends AbstractZeppOsService {
}
}
public void requestItems(final TransactionBuilder builder, final byte type) {
public void requestItems(final ZeppOsTransactionBuilder builder, final byte type) {
LOG.info("Requesting display items type={}", type);
write(builder, new byte[]{CMD_REQUEST, type});
@@ -266,16 +266,13 @@ public class ZeppOsDisplayItemsService extends AbstractZeppOsService {
final List<String> allSettings,
List<String> enabledList,
final FlagsMap flags) {
try {
final TransactionBuilder builder = new TransactionBuilder("set display items type " + menuType);
setDisplayItems(builder, menuType, allSettings, enabledList, flags);
builder.queue(getSupport().getQueue());
} catch (final Exception e) {
LOG.error("Failed to set display items", e);
}
withTransactionBuilder(
"set display items type " + menuType,
builder -> setDisplayItems(builder, menuType, allSettings, enabledList, flags)
);
}
private void setDisplayItems(final TransactionBuilder builder,
private void setDisplayItems(final ZeppOsTransactionBuilder builder,
final byte menuType,
final List<String> allSettings,
List<String> enabledList,
@@ -21,9 +21,9 @@ import android.bluetooth.BluetoothGattCharacteristic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.filetransfer.ZeppOsFileTransferImpl;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.filetransfer.ZeppOsFileTransferV2;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.filetransfer.ZeppOsFileTransferV3;
@@ -77,7 +77,7 @@ public class ZeppOsFileTransferService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
write(builder, new byte[]{ZeppOsFileTransferImpl.CMD_CAPABILITIES_REQUEST});
}
@@ -22,10 +22,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiUtils;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class ZeppOsFindDeviceService extends AbstractZeppOsService {
@@ -128,7 +128,7 @@ public class ZeppOsFindDeviceService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
}
@@ -26,9 +26,9 @@ import java.nio.charset.StandardCharsets;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -133,7 +133,14 @@ public class ZeppOsFtpServerService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
mCallback = null;
final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences()
.withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_ADDRESS, null)
.withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_USERNAME, null)
.withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_STATUS, null);
evaluateGBDeviceEvent(evt);
}
public void setCallback(final Callback callback) {
@@ -16,7 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
import android.bluetooth.BluetoothGattCharacteristic;
import android.os.Handler;
import org.slf4j.Logger;
@@ -26,9 +25,9 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepState
import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.RealtimeSamplesAggregator;
public class ZeppOsHeartRateService extends AbstractZeppOsService {
@@ -111,10 +110,10 @@ public class ZeppOsHeartRateService extends AbstractZeppOsService {
realtimeStarted = true;
realtimeOneShot = true;
final TransactionBuilder builder = new TransactionBuilder("HeartRateTest");
builder.notify(getSupport().getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT), true);
final ZeppOsTransactionBuilder builder = createTransactionBuilder("HeartRateTest");
builder.notify(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT, true);
write(builder, new byte[]{CMD_REALTIME_SET, REALTIME_MODE_START});
builder.queue(getSupport().getQueue());
builder.queue(getSupport());
realtimeHandler.removeCallbacksAndMessages(null);
scheduleContinue();
@@ -133,13 +132,10 @@ public class ZeppOsHeartRateService extends AbstractZeppOsService {
realtimeStarted = enable;
realtimeOneShot = false;
final TransactionBuilder builder = new TransactionBuilder("set realtime heart rate measurement = " + enable);
final BluetoothGattCharacteristic hrCharacteristic = getSupport().getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT);
if (hrCharacteristic != null) {
builder.notify(getSupport().getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT), enable);
}
final ZeppOsTransactionBuilder builder = createTransactionBuilder("set realtime heart rate measurement = " + enable);
builder.notify(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT, enable);
write(builder, new byte[]{CMD_REALTIME_SET, hrCmd});
builder.queue(getSupport().getQueue());
builder.queue(getSupport());
realtimeHandler.removeCallbacksAndMessages(null);
if (enable) {
@@ -178,8 +174,6 @@ public class ZeppOsHeartRateService extends AbstractZeppOsService {
}
private void sendContinue() {
final TransactionBuilder builder = new TransactionBuilder("hr continue");
write(builder, new byte[]{CMD_REALTIME_SET, REALTIME_MODE_CONTINUE});
builder.queue(getSupport().getQueue());
write("hr continue", new byte[]{CMD_REALTIME_SET, REALTIME_MODE_CONTINUE});
}
}
@@ -35,6 +35,7 @@ import java.util.Map;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsWeather;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
@@ -120,6 +121,12 @@ public class ZeppOsHttpService extends AbstractZeppOsService {
}
}
@Override
public void initialize(final ZeppOsTransactionBuilder builder) {
urlToLocalFile.clear();
urlDownloadCallbacks.clear();
}
public void registerForDownload(final String url, final Uri uri, @Nullable final Callback callback) {
this.urlToLocalFile.put(url, uri);
this.urlDownloadCallbacks.put(url, callback);
@@ -32,9 +32,9 @@ import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -119,11 +119,8 @@ public class ZeppOsLogsService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
requestCapabilities(builder);
}
public void requestCapabilities(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
sessions.clear();
write(builder, CMD_CAPABILITIES_REQUEST);
}
@@ -30,9 +30,9 @@ import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.BarcodeFormat;
import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@@ -116,7 +116,7 @@ public class ZeppOsLoyaltyCardService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestCapabilities(builder);
}
@@ -128,7 +128,7 @@ public class ZeppOsLoyaltyCardService extends AbstractZeppOsService {
return supportedFormats;
}
public void requestCapabilities(final TransactionBuilder builder) {
public void requestCapabilities(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
}
@@ -34,9 +34,9 @@ import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.operations.ZeppOsMapsFile;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -63,7 +63,7 @@ public class ZeppOsMapsService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
}
@@ -26,15 +26,16 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.MapUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -54,15 +55,15 @@ public class ZeppOsMorningUpdatesService extends AbstractZeppOsService {
private static final byte CMD_CATEGORIES_SET_ACK = 0x0a;
private static final Map<Byte, String> MORNING_UPDATES_MAP = new HashMap<Byte, String>() {{
put((byte) 0x02, "weather");
put((byte) 0x03, "battery");
put((byte) 0x04, "sleep");
put((byte) 0x06, "event");
put((byte) 0x07, "pai");
put((byte) 0x08, "yesterdays_activity");
put((byte) 0x09, "zepp_coach");
put((byte) 0x0a, "cycle_tracking");
put((byte) 0x0b, "readiness");
put((byte) 0x02, "weather");
put((byte) 0x03, "battery");
put((byte) 0x04, "sleep");
put((byte) 0x06, "event");
put((byte) 0x07, "pai");
put((byte) 0x08, "yesterdays_activity");
put((byte) 0x09, "zepp_coach");
put((byte) 0x0a, "cycle_tracking");
put((byte) 0x0b, "readiness");
}};
public ZeppOsMorningUpdatesService(ZeppOsSupport support) {
@@ -128,12 +129,12 @@ public class ZeppOsMorningUpdatesService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
getEnabled(builder);
getCategories(builder);
}
public void getEnabled(final TransactionBuilder builder) {
public void getEnabled(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_ENABLED_GET);
}
@@ -141,7 +142,7 @@ public class ZeppOsMorningUpdatesService extends AbstractZeppOsService {
write("set morning updates enabled", new byte[] {CMD_ENABLED_SET, bool(enabled)});
}
public void getCategories(final TransactionBuilder builder) {
public void getCategories(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CATEGORIES_REQUEST);
}
@@ -165,12 +166,12 @@ public class ZeppOsMorningUpdatesService extends AbstractZeppOsService {
for (final String category : categoriesSorted) {
final byte id;
if (idMap.containsKey(category)) {
id = idMap.get(category);
id = Objects.requireNonNull(idMap.get(category));
} else {
// name doesn't match a known value, attempt to parse it as hex
final Matcher matcher = Pattern.compile("^0[xX]([0-9a-fA-F]{1,2})$").matcher(category);
if (matcher.find()) {
id = (byte) Integer.parseInt(matcher.group(1), 16);
id = (byte) Integer.parseInt(Objects.requireNonNull(matcher.group(1)), 16);
} else {
LOG.warn("Unknown category {}, and failed to parse as hex, not setting", category);
return;
@@ -16,6 +16,9 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
import android.os.Handler;
import android.os.Looper;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,6 +29,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager;
public class ZeppOsMusicService extends AbstractZeppOsService {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsMusicService.class);
@@ -44,6 +49,10 @@ public class ZeppOsMusicService extends AbstractZeppOsService {
private static final byte BUTTON_VOLUME_UP = 0x05;
private static final byte BUTTON_VOLUME_DOWN = 0x06;
private final Handler handler = new Handler();
private MediaManager mediaManager;
protected boolean isMusicAppStarted = false;
public ZeppOsMusicService(final ZeppOsSupport support) {
super(support, false);
}
@@ -103,16 +112,42 @@ public class ZeppOsMusicService extends AbstractZeppOsService {
}
}
@Override
public void initialize(final ZeppOsTransactionBuilder builder) {
if (mediaManager == null) {
mediaManager = new MediaManager(getContext());
}
}
@Override
public void dispose() {
handler.removeCallbacksAndMessages(null);
}
public void onSetMusicState(final MusicStateSpec stateSpec) {
if (mediaManager.onSetMusicState(stateSpec) && isMusicAppStarted) {
sendMusicState(null, mediaManager.getBufferMusicStateSpec());
}
}
public void onSetMusicInfo(final MusicSpec musicSpec) {
if (mediaManager.onSetMusicInfo(musicSpec) && isMusicAppStarted) {
sendMusicState(mediaManager.getBufferMusicSpec(), mediaManager.getBufferMusicStateSpec());
}
}
private void onMusicAppOpen() {
getSupport().onMusicAppOpen();
isMusicAppStarted = true;
sendMusicStateDelayed();
}
private void onMusicAppClosed() {
getSupport().onMusicAppClosed();
LOG.info("Music app terminated");
isMusicAppStarted = false;
}
public void sendMusicState(final MusicSpec musicSpec,
final MusicStateSpec musicStateSpec) {
private void sendMusicState(final MusicSpec musicSpec,
final MusicStateSpec musicStateSpec) {
LOG.info("Sending music: {}, {}", musicSpec, musicStateSpec);
// TODO: Encode not playing state (flag 0x20, single 0x01 byte before volume)
@@ -124,6 +159,19 @@ public class ZeppOsMusicService extends AbstractZeppOsService {
write("send music state", cmd);
}
/**
* Send the music state after a small delay. If we send it right as the app notifies us that it opened,
* it won't be recognized.
*/
private void sendMusicStateDelayed() {
final Looper mainLooper = Looper.getMainLooper();
new Handler(mainLooper).postDelayed(() -> {
mediaManager.refresh();
sendMusicState(mediaManager.getBufferMusicSpec(), mediaManager.getBufferMusicStateSpec());
sendVolume(mediaManager.getPhoneVolume());
}, 100);
}
public void sendVolume(final float volume) {
LOG.info("Sending volume: {}", volume);
@@ -42,10 +42,10 @@ import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsBitmapFormat;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
@@ -103,11 +103,7 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
requestCapabilities(builder);
}
public void requestCapabilities(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
write(builder, NOTIFICATION_CMD_CAPABILITIES_REQUEST);
}
@@ -255,8 +251,6 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
final TransactionBuilder builder = new TransactionBuilder("send notification");
baos.write(NOTIFICATION_CMD_SEND);
// ID
@@ -285,12 +279,12 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
// TODO put this behind a setting?
baos.write(callSpec.number != null ? 0x01 : 0x00); // reply from watch
write(builder, baos.toByteArray());
builder.queue(getSupport().getQueue());
} catch (final Exception e) {
LOG.error("Failed to send call", e);
return;
}
write("send notification", baos.toByteArray());
}
public void sendNotification(final NotificationSpec notificationSpec) {
@@ -306,8 +300,6 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
// TODO Check real limit for notificationMaxLength / respect across all fields
try {
final TransactionBuilder builder = new TransactionBuilder("send notification");
baos.write(NOTIFICATION_CMD_SEND);
baos.write(BLETypeConversions.fromUint32(notificationSpec.getId()));
if (notificationSpec.type == NotificationType.GENERIC_SMS) {
@@ -380,12 +372,12 @@ public class ZeppOsNotificationService extends AbstractZeppOsService {
}
baos.write(0);
}
write(builder, baos.toByteArray());
builder.queue(getSupport().getQueue());
} catch (final Exception e) {
LOG.error("Failed to send notification", e);
return;
}
write("send notification", baos.toByteArray());
}
public void deleteNotification(final int id) {
@@ -16,9 +16,13 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.content.pm.PackageManager;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresPermission;
import androidx.core.app.ActivityCompat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,9 +33,9 @@ import java.nio.charset.StandardCharsets;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class ZeppOsPhoneService extends AbstractZeppOsService {
@@ -130,7 +134,7 @@ public class ZeppOsPhoneService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
if (getCoordinator().supportsBluetoothPhoneCalls(getSupport().getDevice())) {
requestCapabilities(builder);
requestEnabled(builder);
@@ -141,14 +145,15 @@ public class ZeppOsPhoneService extends AbstractZeppOsService {
return version == 1;
}
public void requestCapabilities(final TransactionBuilder builder) {
public void requestCapabilities(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
}
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public void getPairedStatus() {
final String bluetoothName = getBluetoothName();
if (bluetoothName == null) {
LOG.error("bluetoothName is null");
LOG.error("bluetoothName for paired status check is null");
return;
}
@@ -165,9 +170,13 @@ public class ZeppOsPhoneService extends AbstractZeppOsService {
}
public void startPairing() {
if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
LOG.error("Bluetooth permission not granted");
return;
}
final String bluetoothName = getBluetoothName();
if (bluetoothName == null) {
LOG.error("bluetoothName is null");
LOG.error("bluetoothName for pairing is null");
return;
}
@@ -183,7 +192,7 @@ public class ZeppOsPhoneService extends AbstractZeppOsService {
write("start phone pairing", buf.array());
}
public void requestEnabled(final TransactionBuilder builder) {
public void requestEnabled(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_ENABLED_REQUEST);
}
@@ -198,6 +207,7 @@ public class ZeppOsPhoneService extends AbstractZeppOsService {
write("set phone enabled", cmd);
}
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
@Nullable
public String getBluetoothName() {
final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -37,9 +37,9 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePref
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -107,9 +107,7 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
case CMD_RESPONSE:
LOG.info("Got reminders from band");
decodeAndUpdateReminders(payload);
final TransactionBuilder builder = getSupport().createTransactionBuilder("send reminders");
sendReminders(builder);
builder.queue(getSupport().getQueue());
withTransactionBuilder("send reminders", this::sendReminders);
return;
default:
LOG.warn("Unexpected reminders payload byte {}", String.format("0x%02x", payload[0]));
@@ -117,25 +115,25 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestCapabilities(builder);
requestReminders(builder);
}
private void requestCapabilities(final TransactionBuilder builder) {
private void requestCapabilities(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
}
private void requestReminders(final TransactionBuilder builder) {
private void requestReminders(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_REQUEST);
}
public void sendReminders(final TransactionBuilder builder) {
public void sendReminders(final ZeppOsTransactionBuilder builder) {
final List<? extends Reminder> reminders = DBHelper.getReminders(getSupport().getDevice());
sendReminders(builder, reminders);
}
public void sendReminders(final TransactionBuilder builder, final List<? extends Reminder> reminders) {
public void sendReminders(final ZeppOsTransactionBuilder builder, final List<? extends Reminder> reminders) {
LOG.info("On Set Reminders: {}", reminders.size());
final int reminderSlotCount = getCoordinator().getReminderSlotCount(getSupport().getDevice());
@@ -209,7 +207,7 @@ public class ZeppOsRemindersService extends AbstractZeppOsService {
}
}
private void sendReminderToDevice(final TransactionBuilder builder, int position, final boolean update, final ZeppOsReminder reminder) {
private void sendReminderToDevice(final ZeppOsTransactionBuilder builder, int position, final boolean update, final ZeppOsReminder reminder) {
final DeviceCoordinator coordinator = getCoordinator();
final int reminderSlotCount = coordinator.getReminderSlotCount(getSupport().getDevice());
if (position + 1 > reminderSlotCount) {
@@ -22,9 +22,9 @@ import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
public class ZeppOsServicesService extends AbstractZeppOsService {
private static final Logger LOG = LoggerFactory.getLogger(ZeppOsServicesService.class);
@@ -43,6 +43,7 @@ public class ZeppOsServicesService extends AbstractZeppOsService {
return ENDPOINT;
}
/** @noinspection SwitchStatementWithTooFewBranches*/
@Override
public void handlePayload(final byte[] payload) {
switch (payload[0]) {
@@ -54,7 +55,7 @@ public class ZeppOsServicesService extends AbstractZeppOsService {
}
}
public void requestServices(final TransactionBuilder builder) {
public void requestServices(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_GET_LIST);
}
@@ -32,15 +32,16 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -158,6 +159,7 @@ public class ZeppOsShortcutCardsService extends AbstractZeppOsService {
}
}
/** @noinspection SwitchStatementWithTooFewBranches*/
@Override
public boolean onSendConfiguration(final String config, final Prefs prefs) {
switch (config) {
@@ -172,16 +174,8 @@ public class ZeppOsShortcutCardsService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
requestCapabilities(builder);
requestShortcutCards(builder);
}
public void requestCapabilities(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CAPABILITIES_REQUEST);
}
public void requestShortcutCards(final TransactionBuilder builder) {
write(builder, CMD_LIST_GET);
}
@@ -254,9 +248,9 @@ public class ZeppOsShortcutCardsService extends AbstractZeppOsService {
}
try {
baos.write(appNum.getBytes(StandardCharsets.UTF_8));
baos.write(Objects.requireNonNull(appNum).getBytes(StandardCharsets.UTF_8));
baos.write(0);
baos.write(cardNum.getBytes(StandardCharsets.UTF_8));
baos.write(Objects.requireNonNull(cardNum).getBytes(StandardCharsets.UTF_8));
baos.write(0);
baos.write(1); // enabled
baos.write(0); // ?
@@ -34,9 +34,9 @@ import java.time.LocalDate;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class ZeppOsUserInfoService extends AbstractZeppOsService {
@@ -69,10 +69,11 @@ public class ZeppOsUserInfoService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
setUserInfo(builder);
}
/** @noinspection EnhancedSwitchMigration*/
@Override
public boolean onSendConfiguration(final String config, final Prefs prefs) {
switch (config) {
@@ -82,16 +83,14 @@ public class ZeppOsUserInfoService extends AbstractZeppOsService {
case PREF_USER_HEIGHT_CM:
case PREF_USER_GENDER:
case PREF_DEVICE_REGION:
final TransactionBuilder builder = getSupport().createTransactionBuilder("set user info");
setUserInfo(builder);
builder.queue(getSupport().getQueue());
withTransactionBuilder("set user info", this::setUserInfo);
return true;
}
return false;
}
public void setUserInfo(final TransactionBuilder builder) {
public void setUserInfo(final ZeppOsTransactionBuilder builder) {
LOG.info("Attempting to set user info...");
final Prefs prefs = GBApplication.getPrefs();
@@ -31,11 +31,11 @@ import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiUtils;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiVibrationPatternNotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class ZeppOsVibrationPatternsService extends AbstractZeppOsService {
@@ -68,7 +68,7 @@ public class ZeppOsVibrationPatternsService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
for (final HuamiVibrationPatternNotificationType type : getCoordinator().getVibrationPatternNotificationTypes(getSupport().getDevice())) {
// FIXME: Can we read these from the band?
final String typeKey = type.name().toLowerCase(Locale.ROOT);
@@ -81,9 +81,10 @@ public class ZeppOsVibrationPatternsService extends AbstractZeppOsService {
if (config.startsWith(HuamiConst.PREF_HUAMI_VIBRATION_PROFILE_PREFIX) ||
config.startsWith(HuamiConst.PREF_HUAMI_VIBRATION_COUNT_PREFIX) ||
config.startsWith(HuamiConst.PREF_HUAMI_VIBRATION_TRY_PREFIX)) {
final TransactionBuilder builder = new TransactionBuilder("send " + config);
setVibrationPattern(builder, config);
builder.queue(getSupport().getQueue());
withTransactionBuilder(
"send " + config,
builder -> setVibrationPattern(builder, config)
);
return true;
}
@@ -91,7 +92,7 @@ public class ZeppOsVibrationPatternsService extends AbstractZeppOsService {
}
protected void setVibrationPattern(final TransactionBuilder builder, final String preferenceKey) {
protected void setVibrationPattern(final ZeppOsTransactionBuilder builder, final String preferenceKey) {
// The preference key has one of the 3 prefixes
final String notificationTypeName = preferenceKey.replace(PREF_HUAMI_VIBRATION_COUNT_PREFIX, "")
.replace(PREF_HUAMI_VIBRATION_PROFILE_PREFIX, "")
@@ -109,7 +110,7 @@ public class ZeppOsVibrationPatternsService extends AbstractZeppOsService {
setVibrationPattern(builder, notificationType, isTry, vibrationProfile);
}
private void setVibrationPattern(final TransactionBuilder builder,
private void setVibrationPattern(final ZeppOsTransactionBuilder builder,
final HuamiVibrationPatternNotificationType notificationType,
final boolean test,
final VibrationProfile profile) {
@@ -44,6 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.repository.AudioRecordingsR
import nodomain.freeyourgadget.gadgetbridge.entities.AudioRecording;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -132,6 +133,14 @@ public class ZeppOsVoiceMemosService extends AbstractZeppOsService {
LOG.warn("Unexpected voice memos byte {}", String.format("0x%02x", payload[0]));
}
@Override
public void initialize(final ZeppOsTransactionBuilder builder) {
downloadingRecordings.clear();
downloadQueue.clear();
downloading = false;
handler.removeCallbacksAndMessages(null);
}
public void requestList() {
write("get voice memos list", CMD_LIST_REQUEST);
}
@@ -39,9 +39,9 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class ZeppOsWatchfaceService extends AbstractZeppOsService {
@@ -171,7 +171,7 @@ public class ZeppOsWatchfaceService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
requestWatchfaces(builder);
requestCurrentWatchface(builder);
}
@@ -180,21 +180,15 @@ public class ZeppOsWatchfaceService extends AbstractZeppOsService {
return watchfaces;
}
public void requestWatchfaces(final TransactionBuilder builder) {
public void requestWatchfaces(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_LIST_GET);
}
public void requestCurrentWatchface() {
try {
final TransactionBuilder builder = new TransactionBuilder("request current watchface");
requestCurrentWatchface(builder);
builder.queue(getSupport().getQueue());
} catch (final Exception e) {
LOG.error("Failed to request current watchface", e);
}
withTransactionBuilder("request current watchface", this::requestCurrentWatchface);
}
public void requestCurrentWatchface(final TransactionBuilder builder) {
public void requestCurrentWatchface(final ZeppOsTransactionBuilder builder) {
write(builder, CMD_CURRENT_GET);
}
@@ -24,7 +24,6 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
@@ -79,9 +78,7 @@ public class ZeppOsWeatherService extends AbstractZeppOsService {
baos.write(locationName.getBytes(StandardCharsets.UTF_8));
baos.write((byte) 0x00); // ?
final TransactionBuilder builder = new TransactionBuilder("set weather location");
write(builder, baos.toByteArray());
builder.queue(getSupport().getQueue());
write("set weather location", baos.toByteArray());
} catch (final Exception e) {
LOG.error("Failed to set weather location", e);
}
@@ -28,9 +28,9 @@ import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -127,8 +127,12 @@ public class ZeppOsWifiService extends AbstractZeppOsService {
}
@Override
public void initialize(final TransactionBuilder builder) {
public void initialize(final ZeppOsTransactionBuilder builder) {
mCallback = null;
final GBDeviceEventUpdatePreferences evt = new GBDeviceEventUpdatePreferences()
.withPreference(DeviceSettingsPreferenceConst.WIFI_HOTSPOT_STATUS, null);
evaluateGBDeviceEvent(evt);
}
public void setCallback(final Callback callback) {
@@ -21,7 +21,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.service
/**
* Wrapper class to keep track of ongoing file send requests and their progress.
*/
class FileTransferRequest {
public class FileTransferRequest {
private final String url;
private final String filename;
private final int rawLength;
@@ -19,8 +19,8 @@ import java.util.zip.Inflater;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@@ -117,10 +117,10 @@ public abstract class ZeppOsFileTransferImpl {
// TODO: 3 unknown bytes for v3
final TransactionBuilder builder = mSupport.createTransactionBuilder("enable file transfer v3 notifications");
builder.notify(mSupport.getCharacteristic(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3_SEND), true);
builder.notify(mSupport.getCharacteristic(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3_RECEIVE), true);
builder.queue(mSupport.getQueue());
final ZeppOsTransactionBuilder builder = mSupport.createZeppOsTransactionBuilder("enable file transfer v3 notifications");
builder.notify(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3_SEND, true);
builder.notify(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3_RECEIVE, true);
builder.queue(mSupport);
}
mSupport.evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_SUPPORTED_SERVICES, new HashSet<>(supportedServices)));
@@ -33,6 +33,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsTransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsFileTransferService;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
@@ -65,6 +66,7 @@ public class ZeppOsFileTransferV3 extends ZeppOsFileTransferImpl {
super(fileTransferService, support);
}
/** @noinspection SwitchStatementWithTooFewBranches*/
@Override
public void handlePayload(final byte[] payload) {
switch (payload[0]) {
@@ -256,15 +258,12 @@ public class ZeppOsFileTransferV3 extends ZeppOsFileTransferImpl {
final byte[] payload = buf.array();
final TransactionBuilder builder = mSupport.createTransactionBuilder("send chunk v3");
final ZeppOsTransactionBuilder builder = mSupport.createZeppOsTransactionBuilder("send chunk v3");
for (int i = 0; i < payload.length; i += partSize) {
final byte[] part = ArrayUtils.subarray(payload, i, i + partSize);
builder.write(
mSupport.getCharacteristic(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3_SEND),
part
);
builder.write(HuamiService.UUID_CHARACTERISTIC_ZEPP_OS_FILE_TRANSFER_V3_SEND, part);
}
builder.queue(mSupport.getQueue());
builder.queue(mSupport);
request.setProgress(request.getProgress() + chunk.length);
request.setIndex(request.getIndex() + 1);
@@ -32,7 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
public class DevicePrefs extends Prefs {
private GBDevice gbDevice;
private final GBDevice gbDevice;
public DevicePrefs(final SharedPreferences preferences, GBDevice gbDevice) {
super(preferences);
@@ -115,4 +115,8 @@ public class DevicePrefs extends Prefs {
return getInt(DeviceSettingsPreferenceConst.PREF_RESERVE_REMINDERS_CALENDAR, 9);
}
public boolean allowHighMtu() {
return getBoolean(PREF_ALLOW_HIGH_MTU, true);
}
}