GloryFit: SOS contact (experimental)

This commit is contained in:
José Rebelo
2025-12-31 20:54:58 +00:00
parent 921e1f8254
commit d5de4503e9
6 changed files with 110 additions and 13 deletions
@@ -305,6 +305,8 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_WORLD_CLOCKS = "pref_world_clocks";
public static final String PREF_CONTACTS = "pref_contacts";
public static final String PREF_SOS_CONTACT_NAME = "sos_contact_name";
public static final String PREF_SOS_CONTACT_NUMBER = "sos_contact_number";
public static final String PREF_WIDGETS = "pref_widgets";
public static final String PREF_MUSIC_MANAGEMENT = "pref_music_management";
@@ -842,6 +842,8 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
addPreferenceHandlerFor(PREF_MEDIA_PLAYBACK_MODE);
addPreferenceHandlerFor(PREF_SHOKZ_EQUALIZER_BLUETOOTH);
addPreferenceHandlerFor(PREF_SHOKZ_EQUALIZER_MP3);
addPreferenceHandlerFor(PREF_SOS_CONTACT_NAME);
addPreferenceHandlerFor(PREF_SOS_CONTACT_NUMBER);
addPreferenceHandlerFor(PREF_SHOKZ_CONTROLS_LONG_PRESS_MULTI_FUNCTION);
addPreferenceHandlerFor(PREF_SHOKZ_CONTROLS_SIMULTANEOUS_VOLUME_UP_DOWN);
@@ -188,6 +188,10 @@ abstract class GloryFitCoordinator : AbstractBLEDeviceCoordinator() {
)
}
override fun getSupportedDeviceSpecificExperimentalSettings(device: GBDevice): IntArray? {
return intArrayOf(R.xml.devicesettings_sos_contact)
}
override fun getDeviceSpecificSettings(device: GBDevice): DeviceSpecificSettings {
val deviceSpecificSettings = DeviceSpecificSettings()
@@ -49,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.VibrationKind
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager
import nodomain.freeyourgadget.gadgetbridge.util.kotlin.coerceIn
import org.apache.commons.lang3.ArrayUtils
import org.apache.commons.lang3.StringUtils
import org.slf4j.Logger
@@ -59,7 +60,6 @@ import java.util.Calendar
import java.util.GregorianCalendar
import java.util.Locale
import java.util.UUID
import nodomain.freeyourgadget.gadgetbridge.util.kotlin.coerceIn
class GloryFitSupport() : AbstractBTLESingleDeviceSupport(LOG) {
init {
@@ -126,6 +126,7 @@ class GloryFitSupport() : AbstractBTLESingleDeviceSupport(LOG) {
}
setEnableCallRejection(builder)
setEnableSmsReply(builder)
setSosContact(builder)
// FIXME this is probably too early
builder.setDeviceState(GBDevice.State.INITIALIZED)
@@ -414,6 +415,11 @@ class GloryFitSupport() : AbstractBTLESingleDeviceSupport(LOG) {
setSpo2Measurement(builder)
}
DeviceSettingsPreferenceConst.PREF_SOS_CONTACT_NAME,
DeviceSettingsPreferenceConst.PREF_SOS_CONTACT_NUMBER -> {
setSosContact(builder)
}
DeviceSettingsPreferenceConst.PREF_ENABLE_SMS_QUICK_REPLY -> {
setEnableSmsReply(builder)
}
@@ -473,21 +479,12 @@ class GloryFitSupport() : AbstractBTLESingleDeviceSupport(LOG) {
val encodedContacts: MutableList<ByteArray> = mutableListOf()
for (contact in contacts) {
val numberBytes = contact.number.toByteArray()
val nameBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateUtf16BE(contact.name, 20)
val numberNameBytes = encodeContact(contact.name, contact.number) ?: continue
if (numberBytes.size > 15) {
LOG.warn("Contact number length {} too long, skipping", contact.number.length)
continue
}
val buf = ByteBuffer.allocate(4 + numberBytes.size + nameBytes.size).order(ByteOrder.BIG_ENDIAN)
val buf = ByteBuffer.allocate(2 + numberNameBytes.size).order(ByteOrder.BIG_ENDIAN)
buf.put(CMD_CONTACTS)
buf.put(CONTACTS_ADD)
buf.put(numberBytes.size.toByte())
buf.put(nameBytes.size.toByte())
buf.put(numberBytes)
buf.put(nameBytes)
buf.put(numberNameBytes)
encodedContacts.add(buf.array())
}
@@ -558,6 +555,24 @@ class GloryFitSupport() : AbstractBTLESingleDeviceSupport(LOG) {
builder.queue()
}
private fun encodeContact(name: String, number: String): ByteArray? {
val numberBytes = number.toByteArray()
val nameBytes = nodomain.freeyourgadget.gadgetbridge.util.StringUtils.truncateUtf16BE(name, 20)
if (numberBytes.size > 15) {
LOG.warn("Contact number length {} too long, skipping", number.length)
return null
}
val buf = ByteBuffer.allocate(2 + numberBytes.size + nameBytes.size).order(ByteOrder.BIG_ENDIAN)
buf.put(numberBytes.size.toByte())
buf.put(nameBytes.size.toByte())
buf.put(numberBytes)
buf.put(nameBytes)
return buf.array()
}
override fun onSetAlarms(alarms: ArrayList<out Alarm>) {
val builder = createTransactionBuilder("set alarms")
@@ -1181,6 +1196,50 @@ class GloryFitSupport() : AbstractBTLESingleDeviceSupport(LOG) {
)
}
private fun setSosContact(builder: TransactionBuilder) {
if (!GBApplication.getPrefs().experimentalSettings()) {
LOG.warn("Experimental settings disabled, ignoring SOS contact")
return
}
val name = devicePrefs.getString(DeviceSettingsPreferenceConst.PREF_SOS_CONTACT_NAME, "")
val number = devicePrefs.getString(DeviceSettingsPreferenceConst.PREF_SOS_CONTACT_NUMBER, "")
if (name.isBlank() || number.isBlank()) {
LOG.warn("SOS contact number or name is blank, ignoring")
return
}
val numberNameBytes = encodeContact(name, number) ?: return
LOG.debug("Setting SOS contact name = {}, number = {}", name, number)
// Clear
builder.write(
UUID_CHARACTERISTIC_GLORYFIT_DATA_WRITE,
*byteArrayOf(CMD_CONTACTS, CONTACT_SOS_CLEAR)
)
// Set - 37 ac 01 37 ac 06 10 35 3536363636004a0061006e006500200044006f006500000000000000000000000000
builder.write(
UUID_CHARACTERISTIC_GLORYFIT_DATA_WRITE,
*ByteBuffer.allocate(244).order(ByteOrder.LITTLE_ENDIAN).apply {
put(CMD_CONTACTS)
put(CONTACT_SOS_SET)
put(0x01.toByte())
put(CMD_CONTACTS)
put(CONTACT_SOS_SET)
put(numberNameBytes)
}.array()
)
// End - 37 ad fd c1
builder.write(
UUID_CHARACTERISTIC_GLORYFIT_DATA_WRITE,
*byteArrayOf(CMD_CONTACTS, CONTACT_SOS_END, 0xfd.toByte(), 0xc1.toByte())
)
}
private fun rearmBatteryStateRequestTimer() {
mHandler.removeCallbacks(mBatteryStateRequestRunnable)
val devicePrefs = getDevicePrefs()
@@ -1313,6 +1372,9 @@ class GloryFitSupport() : AbstractBTLESingleDeviceSupport(LOG) {
const val CONTACTS_START: Byte = 0xfa.toByte()
const val CONTACTS_ADD: Byte = 0xfb.toByte()
const val CONTACTS_END: Byte = 0xfc.toByte()
const val CONTACT_SOS_CLEAR: Byte = 0xae.toByte()
const val CONTACT_SOS_SET: Byte = 0xac.toByte()
const val CONTACT_SOS_END: Byte = 0xad.toByte()
const val CMD_FACTORY_RESET: Byte = 0xad.toByte()
}
}
+1
View File
@@ -4503,4 +4503,5 @@
<string name="prefs_title_battery_allow_bypass_mode">Allow battery bypass</string>
<string name="pref_title_pebble_search_app_updates">Search for app updates</string>
<string name="pref_summary_pebble_search_app_updates">Automatically search for watch app and watchface updates when opening the app manager</string>
<string name="pref_sos_contact_title">SOS contact</string>
</resources>
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/pref_sos_contact_title">
<PreferenceCategory
android:key="pref_header_sos_contact"
android:title="@string/pref_sos_contact_title"
app:iconSpaceReserved="false">
<EditTextPreference
android:key="sos_contact_name"
android:maxLength="20"
android:title="@string/contact_name"
app:iconSpaceReserved="false"
app:useSimpleSummaryProvider="true" />
<EditTextPreference
android:key="sos_contact_number"
android:maxLength="15"
android:title="@string/contact_phone_number"
app:iconSpaceReserved="false"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>