mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
GloryFit: Initial support for Oukitel BT103, Dotn P66D
This commit is contained in:
Generated
+2
@@ -57,6 +57,7 @@
|
|||||||
<w>gfdi</w>
|
<w>gfdi</w>
|
||||||
<w>gideão</w>
|
<w>gideão</w>
|
||||||
<w>girolamo</w>
|
<w>girolamo</w>
|
||||||
|
<w>gloryfit</w>
|
||||||
<w>gobbetti</w>
|
<w>gobbetti</w>
|
||||||
<w>gree</w>
|
<w>gree</w>
|
||||||
<w>greenberg</w>
|
<w>greenberg</w>
|
||||||
@@ -121,6 +122,7 @@
|
|||||||
<w>normano</w>
|
<w>normano</w>
|
||||||
<w>novotny</w>
|
<w>novotny</w>
|
||||||
<w>oraclejdk</w>
|
<w>oraclejdk</w>
|
||||||
|
<w>oukitel</w>
|
||||||
<w>paddleboarding</w>
|
<w>paddleboarding</w>
|
||||||
<w>padel</w>
|
<w>padel</w>
|
||||||
<w>pebblekit</w>
|
<w>pebblekit</w>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class GBDaoGenerator {
|
|||||||
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
final Schema schema = new Schema(104, MAIN_PACKAGE + ".entities");
|
final Schema schema = new Schema(106, MAIN_PACKAGE + ".entities");
|
||||||
|
|
||||||
Entity userAttributes = addUserAttributes(schema);
|
Entity userAttributes = addUserAttributes(schema);
|
||||||
Entity user = addUserInfo(schema, userAttributes);
|
Entity user = addUserInfo(schema, userAttributes);
|
||||||
@@ -166,6 +166,7 @@ public class GBDaoGenerator {
|
|||||||
addMoyoungBloodPressureSample(schema, user, device);
|
addMoyoungBloodPressureSample(schema, user, device);
|
||||||
addMoyoungSleepStageSample(schema, user, device);
|
addMoyoungSleepStageSample(schema, user, device);
|
||||||
addMoyoungStressSample(schema, user, device);
|
addMoyoungStressSample(schema, user, device);
|
||||||
|
addGloryFitStepsSample(schema, user, device);
|
||||||
|
|
||||||
addHuaweiActivitySample(schema, user, device);
|
addHuaweiActivitySample(schema, user, device);
|
||||||
addHuaweiStressSample(schema, user, device);
|
addHuaweiStressSample(schema, user, device);
|
||||||
@@ -204,6 +205,7 @@ public class GBDaoGenerator {
|
|||||||
addGenericStressSample(schema, user, device);
|
addGenericStressSample(schema, user, device);
|
||||||
addGenericHrvValueSample(schema, user, device);
|
addGenericHrvValueSample(schema, user, device);
|
||||||
addGenericTemperatureSample(schema, user, device);
|
addGenericTemperatureSample(schema, user, device);
|
||||||
|
addGenericSleepStageSample(schema, user, device);
|
||||||
|
|
||||||
new DaoGenerator().generateAll(schema, "app/src/main/java");
|
new DaoGenerator().generateAll(schema, "app/src/main/java");
|
||||||
}
|
}
|
||||||
@@ -1155,6 +1157,19 @@ public class GBDaoGenerator {
|
|||||||
return stressSample;
|
return stressSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Entity addGloryFitStepsSample(Schema schema, Entity user, Entity device) {
|
||||||
|
Entity sleepStageSample = addEntity(schema, "GloryFitStepsSample");
|
||||||
|
addCommonTimeSampleProperties("AbstractTimeSample", sleepStageSample, user, device);
|
||||||
|
sleepStageSample.addIntProperty("totalSteps").notNull();
|
||||||
|
sleepStageSample.addIntProperty("runningStart").notNull();
|
||||||
|
sleepStageSample.addIntProperty("runningEnd").notNull();
|
||||||
|
sleepStageSample.addIntProperty("runningSteps").notNull();
|
||||||
|
sleepStageSample.addIntProperty("walkingStart").notNull();
|
||||||
|
sleepStageSample.addIntProperty("walkingEnd").notNull();
|
||||||
|
sleepStageSample.addIntProperty("walkingSteps").notNull();
|
||||||
|
return sleepStageSample;
|
||||||
|
}
|
||||||
|
|
||||||
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
|
||||||
activitySample.setSuperclass(superClass);
|
activitySample.setSuperclass(superClass);
|
||||||
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
||||||
@@ -1874,4 +1889,12 @@ public class GBDaoGenerator {
|
|||||||
temperatureSample.addIntProperty(SAMPLE_TEMPERATURE_TYPE).notNull();
|
temperatureSample.addIntProperty(SAMPLE_TEMPERATURE_TYPE).notNull();
|
||||||
return temperatureSample;
|
return temperatureSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Entity addGenericSleepStageSample(Schema schema, Entity user, Entity device) {
|
||||||
|
Entity sleepStageSample = addEntity(schema, "GenericSleepStageSample");
|
||||||
|
addCommonTimeSampleProperties("AbstractTimeSample", sleepStageSample, user, device);
|
||||||
|
sleepStageSample.addIntProperty("duration").notNull();
|
||||||
|
sleepStageSample.addIntProperty("stage").notNull();
|
||||||
|
return sleepStageSample;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,6 +300,7 @@ tasks.register('cleanGenerated', Delete) {
|
|||||||
delete fileTree('src/main/java/nodomain/freeyourgadget/gadgetbridge/entities') {
|
delete fileTree('src/main/java/nodomain/freeyourgadget/gadgetbridge/entities') {
|
||||||
include '**/*.java'
|
include '**/*.java'
|
||||||
exclude '**/Abstract*.java'
|
exclude '**/Abstract*.java'
|
||||||
|
exclude '**/GenericActivitySample.java'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -158,7 +158,7 @@ public class CameraActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
GBDeviceEventCameraRemote.Event event = GBDeviceEventCameraRemote.intToEvent(intent.getIntExtra(intentExtraEvent, 0));
|
GBDeviceEventCameraRemote.Event event = GBDeviceEventCameraRemote.intToEvent(intent.getIntExtra(intentExtraEvent, 0));
|
||||||
|
|
||||||
LOG.info("Camera received event: " + event.name());
|
LOG.info("Camera received event: {}", event);
|
||||||
|
|
||||||
// Nothing to do for unknown events
|
// Nothing to do for unknown events
|
||||||
|
|
||||||
@@ -176,6 +176,10 @@ public class CameraActivity extends AppCompatActivity {
|
|||||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||||
contentValues
|
contentValues
|
||||||
).build();
|
).build();
|
||||||
|
if (imageCapture == null) {
|
||||||
|
LOG.error("imageCapture is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
|
imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
|
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
|
||||||
|
|||||||
+2
-8
@@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.InputType;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
@@ -28,21 +29,14 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
|
import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||||
|
|
||||||
public class ContactDetails extends AbstractGBActivity {
|
public class ContactDetails extends AbstractGBActivity {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ContactDetails.class);
|
|
||||||
|
|
||||||
private Contact contact;
|
private Contact contact;
|
||||||
private GBDevice device;
|
private GBDevice device;
|
||||||
|
|
||||||
@@ -64,9 +58,9 @@ public class ContactDetails extends AbstractGBActivity {
|
|||||||
|
|
||||||
contactName = findViewById(R.id.contact_name);
|
contactName = findViewById(R.id.contact_name);
|
||||||
contactNumber = findViewById(R.id.contact_number);
|
contactNumber = findViewById(R.id.contact_number);
|
||||||
|
contactNumber.setInputType(InputType.TYPE_CLASS_PHONE);
|
||||||
|
|
||||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||||
final DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
|
||||||
|
|
||||||
contactName.addTextChangedListener(new TextWatcher() {
|
contactName.addTextChangedListener(new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+21
-14
@@ -17,15 +17,13 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications;
|
package nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
@@ -36,7 +34,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.entities.AppSpecificNotificationSetting;
|
import nodomain.freeyourgadget.gadgetbridge.entities.AppSpecificNotificationSetting;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
|
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
|
||||||
|
|
||||||
public class AppSpecificNotificationSettingsDetailActivity extends AbstractGBActivity {
|
public class AppSpecificNotificationSettingsDetailActivity extends AbstractGBActivity {
|
||||||
private AppSpecificNotificationSettingsRepository repository = null;
|
private AppSpecificNotificationSettingsRepository repository = null;
|
||||||
@@ -45,13 +42,16 @@ public class AppSpecificNotificationSettingsDetailActivity extends AbstractGBAct
|
|||||||
private GBDevice mDevice;
|
private GBDevice mDevice;
|
||||||
private DeviceCoordinator mCoordinator;
|
private DeviceCoordinator mCoordinator;
|
||||||
|
|
||||||
private List<String> mLedPatternValues = new ArrayList<>();
|
private final List<String> mLedPatternValues = new ArrayList<>();
|
||||||
private List<String> mVibrationPatternValues = new ArrayList<>();
|
private final List<String> mVibrationPatternValues = new ArrayList<>();
|
||||||
private List<String> mVibrationCountValues = new ArrayList<>();
|
private final List<String> mVibrationCountValues = new ArrayList<>();
|
||||||
|
|
||||||
private Spinner mSpinnerLedPattern;
|
private Spinner mSpinnerLedPattern;
|
||||||
private Spinner mSpinnerVibrationPattern;
|
private Spinner mSpinnerVibrationPattern;
|
||||||
private Spinner mSpinnerVibrationCount;
|
private Spinner mSpinnerVibrationCount;
|
||||||
|
private TextView mTextViewLedColorTitle;
|
||||||
|
private TextView mTextViewVibration;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -61,6 +61,8 @@ public class AppSpecificNotificationSettingsDetailActivity extends AbstractGBAct
|
|||||||
mSpinnerLedPattern = findViewById(R.id.spinnerLedType);
|
mSpinnerLedPattern = findViewById(R.id.spinnerLedType);
|
||||||
mSpinnerVibrationPattern = findViewById(R.id.spinnerVibraType);
|
mSpinnerVibrationPattern = findViewById(R.id.spinnerVibraType);
|
||||||
mSpinnerVibrationCount = findViewById(R.id.spinnerVibraCount);
|
mSpinnerVibrationCount = findViewById(R.id.spinnerVibraCount);
|
||||||
|
mTextViewLedColorTitle = findViewById(R.id.textViewLedColorTitle);
|
||||||
|
mTextViewVibration = findViewById(R.id.textViewVibration);
|
||||||
|
|
||||||
mDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
mDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||||
mCoordinator = mDevice.getDeviceCoordinator();
|
mCoordinator = mDevice.getDeviceCoordinator();
|
||||||
@@ -78,29 +80,34 @@ public class AppSpecificNotificationSettingsDetailActivity extends AbstractGBAct
|
|||||||
mVibrationCountValues.add(p.getValue());
|
mVibrationCountValues.add(p.getValue());
|
||||||
|
|
||||||
if(!mCoordinator.supportsNotificationLedPatterns()) {
|
if(!mCoordinator.supportsNotificationLedPatterns()) {
|
||||||
mSpinnerLedPattern.setEnabled(false);
|
mTextViewLedColorTitle.setVisibility(View.GONE);
|
||||||
|
mSpinnerLedPattern.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
mSpinnerLedPattern.setAdapter(
|
mSpinnerLedPattern.setAdapter(
|
||||||
createAdapterFromArrayAddingDefault(mCoordinator.getNotificationLedPatterns())
|
createAdapterFromArrayAddingDefault(mCoordinator.getNotificationLedPatterns())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mCoordinator.supportsNotificationVibrationPatterns()) {
|
if (!mCoordinator.supportsNotificationVibrationPatterns()) {
|
||||||
mSpinnerVibrationPattern.setEnabled(false);
|
mSpinnerVibrationPattern.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
mSpinnerVibrationPattern.setAdapter(
|
mSpinnerVibrationPattern.setAdapter(
|
||||||
createAdapterFromArrayAddingDefault(mCoordinator.getNotificationVibrationPatterns())
|
createAdapterFromArrayAddingDefault(mCoordinator.getNotificationVibrationPatterns())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mCoordinator.supportsNotificationVibrationRepetitionPatterns()) {
|
if (!mCoordinator.supportsNotificationVibrationRepetitionPatterns()) {
|
||||||
mSpinnerVibrationCount.setEnabled(false);
|
mSpinnerVibrationCount.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
mSpinnerVibrationCount.setAdapter(
|
mSpinnerVibrationCount.setAdapter(
|
||||||
createAdapterFromArrayAddingDefault(mCoordinator.getNotificationVibrationRepetitionPatterns())
|
createAdapterFromArrayAddingDefault(mCoordinator.getNotificationVibrationRepetitionPatterns())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mCoordinator.supportsNotificationVibrationPatterns() && !mCoordinator.supportsNotificationVibrationRepetitionPatterns()) {
|
||||||
|
mTextViewVibration.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
String title = getIntent().getStringExtra(AppSpecificNotificationSettingsAppListAdapter.STRING_EXTRA_PACKAGE_TITLE);
|
String title = getIntent().getStringExtra(AppSpecificNotificationSettingsAppListAdapter.STRING_EXTRA_PACKAGE_TITLE);
|
||||||
setTitle(title);
|
setTitle(title);
|
||||||
bundleId = getIntent().getStringExtra(AppSpecificNotificationSettingsAppListAdapter.STRING_EXTRA_PACKAGE_NAME);
|
bundleId = getIntent().getStringExtra(AppSpecificNotificationSettingsAppListAdapter.STRING_EXTRA_PACKAGE_NAME);
|
||||||
@@ -152,7 +159,7 @@ public class AppSpecificNotificationSettingsDetailActivity extends AbstractGBAct
|
|||||||
allOptions.add(getString(R.string.pref_default));
|
allOptions.add(getString(R.string.pref_default));
|
||||||
for(AbstractNotificationPattern s: array) allOptions.add(s.getUserReadableName(getApplicationContext()));
|
for(AbstractNotificationPattern s: array) allOptions.add(s.getUserReadableName(getApplicationContext()));
|
||||||
|
|
||||||
return new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, allOptions);
|
return new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, allOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveSettings() {
|
private void saveSettings() {
|
||||||
|
|||||||
+6
@@ -213,6 +213,10 @@ public class DeviceSettingsPreferenceConst {
|
|||||||
public static final String PREF_HEARTRATE_STRESS_RELAXATION_REMINDER = "heartrate_stress_relaxation_reminder";
|
public static final String PREF_HEARTRATE_STRESS_RELAXATION_REMINDER = "heartrate_stress_relaxation_reminder";
|
||||||
public static final String PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING = "heartrate_sleep_breathing_quality_monitoring";
|
public static final String PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING = "heartrate_sleep_breathing_quality_monitoring";
|
||||||
public static final String PREF_SPO2_ALL_DAY_MONITORING = "spo2_all_day_monitoring_enabled";
|
public static final String PREF_SPO2_ALL_DAY_MONITORING = "spo2_all_day_monitoring_enabled";
|
||||||
|
public static final String PREF_SPO2_MEASUREMENT_INTERVAL = "spo2_measurement_interval";
|
||||||
|
public static final String PREF_SPO2_MEASUREMENT_TIME = "spo2_measurement_time";
|
||||||
|
public static final String PREF_SPO2_MEASUREMENT_START = "spo2_measurement_start";
|
||||||
|
public static final String PREF_SPO2_MEASUREMENT_END = "spo2_measurement_end";
|
||||||
public static final String PREF_SPO2_LOW_ALERT_THRESHOLD = "spo2_low_alert_threshold";
|
public static final String PREF_SPO2_LOW_ALERT_THRESHOLD = "spo2_low_alert_threshold";
|
||||||
public static final String PREF_HRV_ALL_DAY_MONITORING = "hrv_all_day_monitoring_enabled";
|
public static final String PREF_HRV_ALL_DAY_MONITORING = "hrv_all_day_monitoring_enabled";
|
||||||
public static final String PREF_TEMPERATURE_ALL_DAY_MONITORING = "continuous_skin_temperature_measurement";
|
public static final String PREF_TEMPERATURE_ALL_DAY_MONITORING = "continuous_skin_temperature_measurement";
|
||||||
@@ -611,10 +615,12 @@ public class DeviceSettingsPreferenceConst {
|
|||||||
public static final String PREF_POWER_SAVING = "pref_key_power_saving";
|
public static final String PREF_POWER_SAVING = "pref_key_power_saving";
|
||||||
public static final String PREF_FORCE_CONNECTION_TYPE = "pref_force_connection_type";
|
public static final String PREF_FORCE_CONNECTION_TYPE = "pref_force_connection_type";
|
||||||
|
|
||||||
|
public static final String PREF_ENABLE_CALL_REJECT = "enable_call_reject";
|
||||||
public static final String PREF_AUTO_REPLY_INCOMING_CALL = "pref_auto_reply_phonecall";
|
public static final String PREF_AUTO_REPLY_INCOMING_CALL = "pref_auto_reply_phonecall";
|
||||||
public static final String PREF_AUTO_REPLY_INCOMING_CALL_DELAY = "pref_auto_reply_phonecall_delay";
|
public static final String PREF_AUTO_REPLY_INCOMING_CALL_DELAY = "pref_auto_reply_phonecall_delay";
|
||||||
public static final String PREF_SPEAK_NOTIFICATIONS_ALOUD = "pref_speak_notifications_aloud";
|
public static final String PREF_SPEAK_NOTIFICATIONS_ALOUD = "pref_speak_notifications_aloud";
|
||||||
public static final String PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE = "pref_speak_notifications_focus_exclusive";
|
public static final String PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE = "pref_speak_notifications_focus_exclusive";
|
||||||
|
public static final String PREF_ENABLE_SMS_QUICK_REPLY = "enable_sms_quick_reply";
|
||||||
|
|
||||||
public static final String PREF_CYCLING_SENSOR_PERSISTENCE_INTERVAL = "pref_cycling_persistence_interval";
|
public static final String PREF_CYCLING_SENSOR_PERSISTENCE_INTERVAL = "pref_cycling_persistence_interval";
|
||||||
public static final String PREF_CYCLING_SENSOR_WHEEL_DIAMETER = "pref_cycling_wheel_diameter";
|
public static final String PREF_CYCLING_SENSOR_WHEEL_DIAMETER = "pref_cycling_wheel_diameter";
|
||||||
|
|||||||
+32
@@ -282,4 +282,36 @@ public final class DeviceSettingsUtils {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void populateWithBpmRange(final CharSequence prefKey,
|
||||||
|
final DeviceSpecificSettingsHandler handler,
|
||||||
|
final int rangeMin,
|
||||||
|
final int rangeMax) {
|
||||||
|
final Preference pref = handler.findPreference(prefKey);
|
||||||
|
if (pref == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeMin >= rangeMax) {
|
||||||
|
throw new IllegalArgumentException("Invalid range [" + rangeMin + ", " + rangeMax + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
final CharSequence[] entries = new CharSequence[rangeMax - rangeMin + 2];
|
||||||
|
final CharSequence[] values = new CharSequence[rangeMax - rangeMin + 2];
|
||||||
|
entries[0] = handler.getContext().getString(R.string.off);
|
||||||
|
values[0] = "0";
|
||||||
|
|
||||||
|
for (int i = 1, bpm = rangeMin; bpm <= rangeMax; i++, bpm++) {
|
||||||
|
entries[i] = handler.getContext().getString(R.string.bpm_value_unit, bpm);
|
||||||
|
values[i] = String.valueOf(bpm);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pref instanceof ListPreference) {
|
||||||
|
((ListPreference) pref).setEntries(entries);
|
||||||
|
((ListPreference) pref).setEntryValues(values);
|
||||||
|
} else if (pref instanceof MultiSelectListPreference) {
|
||||||
|
((MultiSelectListPreference) pref).setEntries(entries);
|
||||||
|
((MultiSelectListPreference) pref).setEntryValues(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -603,6 +603,10 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
|
|||||||
addPreferenceHandlerFor(PREF_HEARTRATE_STRESS_RELAXATION_REMINDER);
|
addPreferenceHandlerFor(PREF_HEARTRATE_STRESS_RELAXATION_REMINDER);
|
||||||
addPreferenceHandlerFor(PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING);
|
addPreferenceHandlerFor(PREF_HEARTRATE_SLEEP_BREATHING_QUALITY_MONITORING);
|
||||||
addPreferenceHandlerFor(PREF_SPO2_ALL_DAY_MONITORING);
|
addPreferenceHandlerFor(PREF_SPO2_ALL_DAY_MONITORING);
|
||||||
|
addPreferenceHandlerFor(PREF_SPO2_MEASUREMENT_INTERVAL);
|
||||||
|
addPreferenceHandlerFor(PREF_SPO2_MEASUREMENT_TIME);
|
||||||
|
addPreferenceHandlerFor(PREF_SPO2_MEASUREMENT_START);
|
||||||
|
addPreferenceHandlerFor(PREF_SPO2_MEASUREMENT_END);
|
||||||
addPreferenceHandlerFor(PREF_SPO2_LOW_ALERT_THRESHOLD);
|
addPreferenceHandlerFor(PREF_SPO2_LOW_ALERT_THRESHOLD);
|
||||||
addPreferenceHandlerFor(PREF_HRV_ALL_DAY_MONITORING);
|
addPreferenceHandlerFor(PREF_HRV_ALL_DAY_MONITORING);
|
||||||
addPreferenceHandlerFor(PREF_TEMPERATURE_ALL_DAY_MONITORING);
|
addPreferenceHandlerFor(PREF_TEMPERATURE_ALL_DAY_MONITORING);
|
||||||
@@ -662,6 +666,8 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
|
|||||||
addPreferenceHandlerFor(PREF_NOTIFICATION_DELAY_CALLS);
|
addPreferenceHandlerFor(PREF_NOTIFICATION_DELAY_CALLS);
|
||||||
addPreferenceHandlerFor(PREF_CALL_REJECT_METHOD);
|
addPreferenceHandlerFor(PREF_CALL_REJECT_METHOD);
|
||||||
addPreferenceHandlerFor(PREF_AUTO_REPLY_INCOMING_CALL);
|
addPreferenceHandlerFor(PREF_AUTO_REPLY_INCOMING_CALL);
|
||||||
|
addPreferenceHandlerFor(PREF_ENABLE_CALL_REJECT);
|
||||||
|
addPreferenceHandlerFor(PREF_ENABLE_SMS_QUICK_REPLY);
|
||||||
|
|
||||||
addPreferenceHandlerFor(PREF_SLEEP_MODE_SLEEP_SCREEN);
|
addPreferenceHandlerFor(PREF_SLEEP_MODE_SLEEP_SCREEN);
|
||||||
addPreferenceHandlerFor(PREF_SLEEP_MODE_SMART_ENABLE);
|
addPreferenceHandlerFor(PREF_SLEEP_MODE_SMART_ENABLE);
|
||||||
|
|||||||
+2
@@ -20,6 +20,7 @@ import android.content.Context;
|
|||||||
|
|
||||||
import androidx.activity.result.ActivityResultCaller;
|
import androidx.activity.result.ActivityResultCaller;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
@@ -35,6 +36,7 @@ public interface DeviceSpecificSettingsHandler extends ActivityResultCaller {
|
|||||||
* @param preferenceKey the preference key.
|
* @param preferenceKey the preference key.
|
||||||
* @return the preference, if found.
|
* @return the preference, if found.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
<T extends Preference> T findPreference(@NonNull CharSequence preferenceKey);
|
<T extends Preference> T findPreference(@NonNull CharSequence preferenceKey);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+8
@@ -20,6 +20,8 @@ package nodomain.freeyourgadget.gadgetbridge.deviceevents;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -54,6 +56,12 @@ public class GBDeviceEventCallControl extends GBDeviceEvent {
|
|||||||
context.sendBroadcast(callIntent);
|
context.sendBroadcast(callIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + "event: " + event;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Event {
|
public enum Event {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
ACCEPT,
|
ACCEPT,
|
||||||
|
|||||||
+31
-23
@@ -19,11 +19,21 @@ package nodomain.freeyourgadget.gadgetbridge.deviceevents;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
|
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
|
||||||
public class GBDeviceEventCameraRemote extends GBDeviceEvent {
|
public class GBDeviceEventCameraRemote extends GBDeviceEvent {
|
||||||
public Event event = Event.UNKNOWN;
|
public Event event;
|
||||||
|
|
||||||
|
public GBDeviceEventCameraRemote() {
|
||||||
|
this(Event.UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GBDeviceEventCameraRemote(Event event) {
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void evaluate(final Context context, final GBDevice device) {
|
public void evaluate(final Context context, final GBDevice device) {
|
||||||
@@ -33,6 +43,12 @@ public class GBDeviceEventCameraRemote extends GBDeviceEvent {
|
|||||||
context.startActivity(cameraIntent);
|
context.startActivity(cameraIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + "event: " + event;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Event {
|
public enum Event {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
OPEN_CAMERA,
|
OPEN_CAMERA,
|
||||||
@@ -42,30 +58,22 @@ public class GBDeviceEventCameraRemote extends GBDeviceEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public int eventToInt(Event event) {
|
static public int eventToInt(Event event) {
|
||||||
switch (event) {
|
return switch (event) {
|
||||||
case UNKNOWN:
|
case UNKNOWN -> 0;
|
||||||
return 0;
|
case OPEN_CAMERA -> 1;
|
||||||
case OPEN_CAMERA:
|
case TAKE_PICTURE -> 2;
|
||||||
return 1;
|
case CLOSE_CAMERA -> 3;
|
||||||
case TAKE_PICTURE:
|
default -> -1;
|
||||||
return 2;
|
};
|
||||||
case CLOSE_CAMERA:
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Event intToEvent(int event) {
|
static public Event intToEvent(int event) {
|
||||||
switch (event) {
|
return switch (event) {
|
||||||
case 0:
|
case 0 -> Event.UNKNOWN;
|
||||||
return Event.UNKNOWN;
|
case 1 -> Event.OPEN_CAMERA;
|
||||||
case 1:
|
case 2 -> Event.TAKE_PICTURE;
|
||||||
return Event.OPEN_CAMERA;
|
case 3 -> Event.CLOSE_CAMERA;
|
||||||
case 2:
|
default -> Event.EXCEPTION;
|
||||||
return Event.TAKE_PICTURE;
|
};
|
||||||
case 3:
|
|
||||||
return Event.CLOSE_CAMERA;
|
|
||||||
}
|
|
||||||
return Event.EXCEPTION;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -26,6 +26,7 @@ import android.content.Intent;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
@@ -75,6 +76,12 @@ public class GBDeviceEventFindPhone extends GBDeviceEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + "event: " + event;
|
||||||
|
}
|
||||||
|
|
||||||
private void handleGBDeviceEventFindPhoneStart(final Context context, final boolean ring) {
|
private void handleGBDeviceEventFindPhoneStart(final Context context, final boolean ring) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // this could be used if app in foreground // TODO: Below Q?
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // this could be used if app in foreground // TODO: Below Q?
|
||||||
final Intent startIntent = new Intent(context, FindPhoneActivity.class);
|
final Intent startIntent = new Intent(context, FindPhoneActivity.class);
|
||||||
|
|||||||
+17
-1
@@ -20,6 +20,8 @@ package nodomain.freeyourgadget.gadgetbridge.deviceevents;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -29,7 +31,15 @@ import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBMusicControlRece
|
|||||||
public class GBDeviceEventMusicControl extends GBDeviceEvent {
|
public class GBDeviceEventMusicControl extends GBDeviceEvent {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventMusicControl.class);
|
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventMusicControl.class);
|
||||||
|
|
||||||
public Event event = Event.UNKNOWN;
|
public Event event;
|
||||||
|
|
||||||
|
public GBDeviceEventMusicControl() {
|
||||||
|
this(Event.UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GBDeviceEventMusicControl(Event event) {
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void evaluate(final Context context, final GBDevice device) {
|
public void evaluate(final Context context, final GBDevice device) {
|
||||||
@@ -40,6 +50,12 @@ public class GBDeviceEventMusicControl extends GBDeviceEvent {
|
|||||||
context.sendBroadcast(musicIntent);
|
context.sendBroadcast(musicIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + "event: " + event;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Event {
|
public enum Event {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
PLAY,
|
PLAY,
|
||||||
|
|||||||
+2
-2
@@ -96,7 +96,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
|||||||
public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceCoordinator.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceCoordinator.class);
|
||||||
|
|
||||||
private Pattern supportedDeviceName = null;
|
protected Pattern supportedDeviceName = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should return a Regexp pattern that will matched against a found device
|
* This method should return a Regexp pattern that will matched against a found device
|
||||||
@@ -237,7 +237,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
|||||||
* Returns a map from {@link AbstractDao} to the corresponding Device ID property. All data present
|
* Returns a map from {@link AbstractDao} to the corresponding Device ID property. All data present
|
||||||
* in these tables for a device will be deleted when the device is deleted.
|
* in these tables for a device will be deleted when the device is deleted.
|
||||||
*/
|
*/
|
||||||
protected Map<AbstractDao<?, ?>, Property> getAllDeviceDao(@NonNull final DaoSession session) {
|
public Map<AbstractDao<?, ?>, Property> getAllDeviceDao(@NonNull final DaoSession session) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/* 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.devices
|
||||||
|
|
||||||
|
import de.greenrobot.dao.AbstractDao
|
||||||
|
import de.greenrobot.dao.Property
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSleepStageSample
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSleepStageSampleDao
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||||
|
|
||||||
|
class GenericSleepStageSampleProvider(
|
||||||
|
device: GBDevice,
|
||||||
|
session: DaoSession
|
||||||
|
) : AbstractTimeSampleProvider<GenericSleepStageSample?>(device, session) {
|
||||||
|
override fun getSampleDao(): AbstractDao<GenericSleepStageSample?, *> {
|
||||||
|
return session.genericSleepStageSampleDao
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTimestampSampleProperty(): Property {
|
||||||
|
return GenericSleepStageSampleDao.Properties.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDeviceIdentifierSampleProperty(): Property {
|
||||||
|
return GenericSleepStageSampleDao.Properties.DeviceId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createSample(): GenericSleepStageSample {
|
||||||
|
return GenericSleepStageSample()
|
||||||
|
}
|
||||||
|
}
|
||||||
-1
@@ -21,7 +21,6 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import de.greenrobot.dao.AbstractDao;
|
import de.greenrobot.dao.AbstractDao;
|
||||||
import de.greenrobot.dao.Property;
|
import de.greenrobot.dao.Property;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSpo2Sample;
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSpo2Sample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSpo2SampleDao;
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSpo2SampleDao;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<AbstractDao<?, ?>, Property> getAllDeviceDao( @NonNull final DaoSession session) {
|
public Map<AbstractDao<?, ?>, Property> getAllDeviceDao( @NonNull final DaoSession session) {
|
||||||
return new HashMap<>() {{
|
return new HashMap<>() {{
|
||||||
put(session.getGarminActivitySampleDao(), GarminActivitySampleDao.Properties.DeviceId);
|
put(session.getGarminActivitySampleDao(), GarminActivitySampleDao.Properties.DeviceId);
|
||||||
put(session.getGarminStressSampleDao(), GarminStressSampleDao.Properties.DeviceId);
|
put(session.getGarminStressSampleDao(), GarminStressSampleDao.Properties.DeviceId);
|
||||||
|
|||||||
+217
@@ -0,0 +1,217 @@
|
|||||||
|
/* 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.devices.gloryfit
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.GenericHeartRateSampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.GenericSleepStageSampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericActivitySample
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.RangeMap
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
open class GloryFitActivitySampleProvider(device: GBDevice, session: DaoSession) :
|
||||||
|
SampleProvider<GenericActivitySample> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG: Logger = LoggerFactory.getLogger(GloryFitActivitySampleProvider::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val stepsProvider: GloryFitStepsSampleProvider = GloryFitStepsSampleProvider(device, session)
|
||||||
|
private val heartRateProvider: GenericHeartRateSampleProvider = GenericHeartRateSampleProvider(device, session)
|
||||||
|
private val sleepStagesProvider: GenericSleepStageSampleProvider = GenericSleepStageSampleProvider(device, session)
|
||||||
|
|
||||||
|
override fun normalizeType(rawType: Int): ActivityKind {
|
||||||
|
return ActivityKind.fromCode(rawType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toRawActivityKind(activityKind: ActivityKind): Int {
|
||||||
|
return activityKind.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun normalizeIntensity(rawIntensity: Int): Float {
|
||||||
|
return rawIntensity.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllActivitySamples(timestampFrom: Int, timestampTo: Int): MutableList<GenericActivitySample> {
|
||||||
|
val byTimestamp: MutableMap<Int, GenericActivitySample> = mutableMapOf()
|
||||||
|
val ret: MutableList<GenericActivitySample> = mutableListOf()
|
||||||
|
|
||||||
|
val stepsSamples = stepsProvider.getAllSamples(timestampFrom * 1000L - 2 * 86400L, timestampTo * 1000L)
|
||||||
|
for (stepsSample in stepsSamples) {
|
||||||
|
val activitySample = GenericActivitySample()
|
||||||
|
activitySample.provider = this
|
||||||
|
activitySample.timestamp = (stepsSample.timestamp / 1000L).toInt()
|
||||||
|
activitySample.steps = stepsSample.totalSteps
|
||||||
|
ret.add(activitySample)
|
||||||
|
byTimestamp.put(activitySample.timestamp, activitySample)
|
||||||
|
}
|
||||||
|
val hrSamples = heartRateProvider.getAllSamples(timestampFrom * 1000L - 2 * 86400L, timestampTo * 1000L)
|
||||||
|
for (hrSample in hrSamples) {
|
||||||
|
val timestamp = (hrSample.timestamp / 1000L).toInt()
|
||||||
|
if (byTimestamp.contains(timestamp)) {
|
||||||
|
byTimestamp[timestamp]!!.heartRate = hrSample.heartRate
|
||||||
|
} else {
|
||||||
|
val activitySample = GenericActivitySample()
|
||||||
|
activitySample.provider = this
|
||||||
|
activitySample.timestamp = timestamp
|
||||||
|
activitySample.heartRate = hrSample.heartRate
|
||||||
|
ret.add(activitySample)
|
||||||
|
byTimestamp.put(activitySample.timestamp, activitySample)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO fill gaps?
|
||||||
|
|
||||||
|
overlaySleep(ret, timestampFrom, timestampTo)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
.filter { sample -> sample.timestamp in timestampFrom..timestampTo }
|
||||||
|
.sortedBy { sample -> sample.timestamp }
|
||||||
|
.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllActivitySamplesHighRes(
|
||||||
|
timestampFrom: Int,
|
||||||
|
timestampTo: Int
|
||||||
|
): MutableList<GenericActivitySample> {
|
||||||
|
return getAllActivitySamples(timestampFrom, timestampTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasHighResData(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActivitySamples(timestampFrom: Int, timestampTo: Int): MutableList<GenericActivitySample> {
|
||||||
|
return getAllActivitySamples(timestampFrom, timestampTo)
|
||||||
|
.filter { sample -> sample.kind == ActivityKind.ACTIVITY }
|
||||||
|
.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addGBActivitySample(activitySample: GenericActivitySample) {
|
||||||
|
throw UnsupportedOperationException("Read-only sample provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addGBActivitySamples(activitySamples: Array<GenericActivitySample>) {
|
||||||
|
throw UnsupportedOperationException("Read-only sample provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createActivitySample(): GenericActivitySample {
|
||||||
|
return GenericActivitySample()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLatestActivitySample(): GenericActivitySample? {
|
||||||
|
// TODO getLatestActivitySample
|
||||||
|
LOG.warn("getLatestActivitySample not implemented");
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLatestActivitySample(until: Int): GenericActivitySample? {
|
||||||
|
// TODO getLatestActivitySample
|
||||||
|
LOG.warn("getLatestActivitySample(until) not implemented");
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFirstActivitySample(): GenericActivitySample? {
|
||||||
|
// TODO getFirstActivitySample
|
||||||
|
LOG.warn("getFirstActivitySample not implemented");
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun overlaySleep(samples: MutableList<GenericActivitySample>, timestampFrom: Int, timestampTo: Int) {
|
||||||
|
val stagesMap = RangeMap<Long, ActivityKind?>(RangeMap.Mode.LOWER_BOUND)
|
||||||
|
|
||||||
|
// Retrieve the last stage before this time range, as the user could have been asleep during
|
||||||
|
// the range transition
|
||||||
|
val lastSleepStageBeforeRange = sleepStagesProvider.getLastSampleBefore(timestampFrom * 1000L)
|
||||||
|
|
||||||
|
if (lastSleepStageBeforeRange != null) {
|
||||||
|
LOG.debug(
|
||||||
|
"Last sleep stage before range: ts={}, stage={}",
|
||||||
|
lastSleepStageBeforeRange.timestamp,
|
||||||
|
lastSleepStageBeforeRange.stage
|
||||||
|
)
|
||||||
|
stagesMap.put(
|
||||||
|
lastSleepStageBeforeRange.timestamp,
|
||||||
|
sleepStageToActivityKind(lastSleepStageBeforeRange.stage)
|
||||||
|
)
|
||||||
|
stagesMap.put(
|
||||||
|
lastSleepStageBeforeRange.timestamp + lastSleepStageBeforeRange.duration * 60 * 1000L,
|
||||||
|
ActivityKind.UNKNOWN
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve all sleep stage samples during the range
|
||||||
|
val sleepStagesInRange = sleepStagesProvider.getAllSamples(
|
||||||
|
timestampFrom * 1000L,
|
||||||
|
timestampTo * 1000L
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!sleepStagesInRange.isEmpty()) {
|
||||||
|
// We got actual sleep stages
|
||||||
|
LOG.debug(
|
||||||
|
"Found {} sleep stage samples between {} and {}",
|
||||||
|
sleepStagesInRange.size,
|
||||||
|
timestampFrom,
|
||||||
|
timestampTo
|
||||||
|
)
|
||||||
|
|
||||||
|
for (stageSample in sleepStagesInRange) {
|
||||||
|
stagesMap.put(
|
||||||
|
stageSample!!.timestamp,
|
||||||
|
sleepStageToActivityKind(stageSample.stage)
|
||||||
|
)
|
||||||
|
stagesMap.put(
|
||||||
|
stageSample.timestamp + stageSample.duration * 60 * 1000L,
|
||||||
|
ActivityKind.UNKNOWN
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stagesMap.isEmpty) {
|
||||||
|
LOG.debug(
|
||||||
|
"Found {} sleep stage samples between {} and {}",
|
||||||
|
stagesMap.size(),
|
||||||
|
timestampFrom,
|
||||||
|
timestampTo
|
||||||
|
)
|
||||||
|
|
||||||
|
for (sample in samples) {
|
||||||
|
val ts = sample.timestamp * 1000L
|
||||||
|
val sleepType = stagesMap.get(ts)
|
||||||
|
if (sleepType != null && sleepType != ActivityKind.UNKNOWN) {
|
||||||
|
sample.rawKind = sleepType.code
|
||||||
|
sample.rawIntensity = ActivitySample.NOT_MEASURED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sleepStageToActivityKind(stage: Int): ActivityKind {
|
||||||
|
return when (stage) {
|
||||||
|
1 -> ActivityKind.DEEP_SLEEP
|
||||||
|
2 -> ActivityKind.LIGHT_SLEEP
|
||||||
|
3 -> ActivityKind.AWAKE_SLEEP
|
||||||
|
4 -> ActivityKind.REM_SLEEP
|
||||||
|
else -> ActivityKind.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+265
@@ -0,0 +1,265 @@
|
|||||||
|
/* 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.devices.gloryfit
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import de.greenrobot.dao.AbstractDao
|
||||||
|
import de.greenrobot.dao.Property
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability.MeasurementInterval
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCardAction
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.GenericSpo2SampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericHeartRateSampleDao
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSleepStageSampleDao
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSpo2SampleDao
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GloryFitStepsSampleDao
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.gloryfit.GloryFitLanguage
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.gloryfit.GloryFitSupport
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.VibrationCount
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.VibrationKind
|
||||||
|
|
||||||
|
abstract class GloryFitCoordinator : AbstractBLEDeviceCoordinator() {
|
||||||
|
override fun getDefaultIconResource(): Int {
|
||||||
|
return R.drawable.ic_device_amazfit_bip
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDeviceSupportClass(device: GBDevice): Class<out DeviceSupport> {
|
||||||
|
return GloryFitSupport::class.java
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAllDeviceDao(session: DaoSession): MutableMap<AbstractDao<*, *>, Property> {
|
||||||
|
return object : HashMap<AbstractDao<*, *>, Property>() {
|
||||||
|
init {
|
||||||
|
put(session.gloryFitStepsSampleDao, GloryFitStepsSampleDao.Properties.DeviceId)
|
||||||
|
put(session.genericSleepStageSampleDao, GenericSleepStageSampleDao.Properties.DeviceId)
|
||||||
|
put(session.genericHeartRateSampleDao, GenericHeartRateSampleDao.Properties.DeviceId)
|
||||||
|
put(session.genericSpo2SampleDao, GenericSpo2SampleDao.Properties.DeviceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSampleProvider(
|
||||||
|
device: GBDevice,
|
||||||
|
session: DaoSession
|
||||||
|
): SampleProvider<out AbstractActivitySample>? {
|
||||||
|
return GloryFitActivitySampleProvider(device, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSpo2SampleProvider(
|
||||||
|
device: GBDevice,
|
||||||
|
session: DaoSession?
|
||||||
|
): TimeSampleProvider<out Spo2Sample?>? {
|
||||||
|
return GenericSpo2SampleProvider(device, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAlarmSlotCount(device: GBDevice): Int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun suggestUnbindBeforePair(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsActivityDataFetching(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsActivityTracking(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsActiveCalories(): Boolean {
|
||||||
|
// TODO it does not, but we could try and match their formula in the samples
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsSpo2(device: GBDevice): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsMusicInfo(): Boolean {
|
||||||
|
// Not info, but supports music control
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCannedRepliesSlotCount(device: GBDevice): Int {
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getContactsSlotCount(device: GBDevice): Int {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsHeartRateMeasurement(device: GBDevice): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsManualHeartRateMeasurement(device: GBDevice): Boolean {
|
||||||
|
return false // TODO supportsManualHeartRateMeasurement
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsRealtimeData(): Boolean {
|
||||||
|
// TODO it does
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsRemSleep(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsAwakeSleep(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsWeather(): Boolean {
|
||||||
|
// TODO it does
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsFindDevice(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsUnicodeEmojis(): Boolean {
|
||||||
|
// Official app seems to just remove them outright
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addBatteryPollingSettings(): Boolean {
|
||||||
|
// It only sends proactive updates during charging
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsNotificationVibrationPatterns(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportsNotificationVibrationRepetitionPatterns(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNotificationVibrationPatterns(): Array<AbstractNotificationPattern?> {
|
||||||
|
return arrayOf(
|
||||||
|
VibrationKind.NONE,
|
||||||
|
VibrationKind.BASIC,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNotificationVibrationRepetitionPatterns(): Array<AbstractNotificationPattern?> {
|
||||||
|
return arrayOf(
|
||||||
|
VibrationCount.ONCE,
|
||||||
|
VibrationCount.TWICE,
|
||||||
|
VibrationCount.THREE,
|
||||||
|
VibrationCount.FOUR,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDeviceSpecificSettings(device: GBDevice): DeviceSpecificSettings {
|
||||||
|
val deviceSpecificSettings = DeviceSpecificSettings()
|
||||||
|
|
||||||
|
val dateTime = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DATE_TIME)
|
||||||
|
dateTime.add(R.xml.devicesettings_timeformat)
|
||||||
|
|
||||||
|
val display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY)
|
||||||
|
display.add(R.xml.devicesettings_liftwrist_display_noshed)
|
||||||
|
|
||||||
|
val health = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.HEALTH)
|
||||||
|
health.add(R.xml.devicesettings_heartrate_automatic_enable)
|
||||||
|
health.add(R.xml.devicesettings_heartrate_alerts)
|
||||||
|
if (supportsSpo2(device)) {
|
||||||
|
health.add(R.xml.devicesettings_spo2)
|
||||||
|
}
|
||||||
|
health.add(R.xml.devicesettings_inactivity_dnd)
|
||||||
|
|
||||||
|
val notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALLS_AND_NOTIFICATIONS)
|
||||||
|
notifications.add(R.xml.devicesettings_transliteration)
|
||||||
|
if (getContactsSlotCount(device) > 0) {
|
||||||
|
notifications.add(R.xml.devicesettings_contacts)
|
||||||
|
}
|
||||||
|
notifications.add(R.xml.devicesettings_header_notifications)
|
||||||
|
notifications.add(R.xml.devicesettings_send_app_notifications)
|
||||||
|
notifications.add(R.xml.devicesettings_per_app_notifications)
|
||||||
|
notifications.add(R.xml.devicesettings_header_phone_calls)
|
||||||
|
notifications.add(R.xml.devicesettings_reject_call_method)
|
||||||
|
if (getCannedRepliesSlotCount(device) > 0) {
|
||||||
|
notifications.add(R.xml.devicesettings_sms_quick_reply)
|
||||||
|
notifications.add(R.xml.devicesettings_canned_reply_16)
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceSpecificSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDeviceSpecificSettingsCustomizer(device: GBDevice): DeviceSpecificSettingsCustomizer? {
|
||||||
|
return GloryFitSettingsCustomizer()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedLanguageSettings(device: GBDevice): Array<String> {
|
||||||
|
// TODO fetch languages from device
|
||||||
|
return arrayOf("auto") + GloryFitLanguage.entries.map { it.locale }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHeartRateMeasurementIntervals(): List<MeasurementInterval?> {
|
||||||
|
// actually on/off
|
||||||
|
return listOf<MeasurementInterval?>(
|
||||||
|
MeasurementInterval.OFF,
|
||||||
|
MeasurementInterval.SMART
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCustomActions(): List<DeviceCardAction> {
|
||||||
|
if (!CameraActivity.supportsCamera()) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
return listOf(
|
||||||
|
object : DeviceCardAction {
|
||||||
|
override fun getIcon(device: GBDevice): Int {
|
||||||
|
return R.drawable.ic_camera_remote
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDescription(device: GBDevice, context: Context): String? {
|
||||||
|
return context.getString(R.string.open_camera)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(device: GBDevice, context: Context) {
|
||||||
|
val cameraIntent = Intent(context, CameraActivity::class.java)
|
||||||
|
cameraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
cameraIntent.putExtra(
|
||||||
|
CameraActivity.intentExtraEvent,
|
||||||
|
GBDeviceEventCameraRemote.eventToInt(GBDeviceEventCameraRemote.Event.OPEN_CAMERA)
|
||||||
|
)
|
||||||
|
context.startActivity(cameraIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
/* 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.devices.gloryfit
|
||||||
|
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class GloryFitSettingsCustomizer : DeviceSpecificSettingsCustomizer {
|
||||||
|
override fun onPreferenceChange(
|
||||||
|
preference: Preference,
|
||||||
|
handler: DeviceSpecificSettingsHandler
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeviceChanged(handler: DeviceSpecificSettingsHandler) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun customizeSettings(
|
||||||
|
handler: DeviceSpecificSettingsHandler,
|
||||||
|
genericDevicePrefs: Prefs,
|
||||||
|
rootKey: String?
|
||||||
|
) {
|
||||||
|
DeviceSettingsUtils.populateWithBpmRange(
|
||||||
|
DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD,
|
||||||
|
handler,
|
||||||
|
100,
|
||||||
|
200
|
||||||
|
)
|
||||||
|
|
||||||
|
DeviceSettingsUtils.populateWithBpmRange(
|
||||||
|
DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD,
|
||||||
|
handler,
|
||||||
|
40,
|
||||||
|
100
|
||||||
|
)
|
||||||
|
|
||||||
|
// Inactivity reminders
|
||||||
|
val dndEnabled = handler.findPreference<Preference>(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND)
|
||||||
|
dndEnabled?.summary = handler.context.getString(R.string.mi2_prefs_inactivity_warnings_dnd_lunch_break_summary)
|
||||||
|
val dndStart = handler.findPreference<Preference>(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_START)
|
||||||
|
dndStart?.isVisible = false
|
||||||
|
val dndEnd = handler.findPreference<Preference>(DeviceSettingsPreferenceConst.PREF_INACTIVITY_DND_END)
|
||||||
|
dndEnd?.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPreferenceKeysWithSummary(): Set<String> {
|
||||||
|
return setOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/* 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.devices.gloryfit;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import de.greenrobot.dao.AbstractDao;
|
||||||
|
import de.greenrobot.dao.Property;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractTimeSampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GloryFitStepsSample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GloryFitStepsSampleDao;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
|
||||||
|
public class GloryFitStepsSampleProvider extends AbstractTimeSampleProvider<GloryFitStepsSample> {
|
||||||
|
public GloryFitStepsSampleProvider(final GBDevice device, final DaoSession session) {
|
||||||
|
super(device, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public AbstractDao<GloryFitStepsSample, ?> getSampleDao() {
|
||||||
|
return getSession().getGloryFitStepsSampleDao();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Property getTimestampSampleProperty() {
|
||||||
|
return GloryFitStepsSampleDao.Properties.Timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Property getDeviceIdentifierSampleProperty() {
|
||||||
|
return GloryFitStepsSampleDao.Properties.DeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GloryFitStepsSample createSample() {
|
||||||
|
return new GloryFitStepsSample();
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/* 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.devices.gloryfit.watches
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.gloryfit.GloryFitCoordinator
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
class DotnP66DCoordinator : GloryFitCoordinator() {
|
||||||
|
override fun isExperimental(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getManufacturer(): String {
|
||||||
|
return "Dotn"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedDeviceName(): Pattern? {
|
||||||
|
return Pattern.compile("^P66D\\(ID-[0-9A-F]{4}\\)$")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDeviceNameResource(): Int {
|
||||||
|
return R.string.devicetype_dotn_p66d
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBondingStyle(): Int {
|
||||||
|
// Watches without calls fail to pair
|
||||||
|
return BONDING_STYLE_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getContactsSlotCount(device: GBDevice): Int {
|
||||||
|
// No phone calls / contacts
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/* 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.devices.gloryfit.watches
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.gloryfit.GloryFitCoordinator
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
class OukitelBT103Coordinator : GloryFitCoordinator() {
|
||||||
|
override fun getManufacturer(): String {
|
||||||
|
return "Oukitel"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedDeviceName(): Pattern? {
|
||||||
|
return Pattern.compile("^BT103\\(ID-[0-9A-F]{4}\\)$")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDeviceNameResource(): Int {
|
||||||
|
return R.string.devicetype_oukitel_bt103
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAlarmSlotCount(device: GBDevice): Int {
|
||||||
|
// 8 slots, but alarms from app are not supported
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-4
@@ -23,11 +23,8 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.hama.fit6900.HamaFit6900DeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.hama.fit6900.HamaFit6900DeviceSupport;
|
||||||
@@ -56,7 +53,7 @@ public final class HamaFit6900DeviceCoordinator extends AbstractBLEDeviceCoordin
|
|||||||
@Override
|
@Override
|
||||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||||
return new int[]{
|
return new int[]{
|
||||||
R.xml.devicesettings_allow_accept_reject_calls, // reject only
|
R.xml.devicesettings_allow_reject_calls, // reject only
|
||||||
R.xml.devicesettings_camera_remote,
|
R.xml.devicesettings_camera_remote,
|
||||||
R.xml.devicesettings_find_phone,
|
R.xml.devicesettings_find_phone,
|
||||||
R.xml.devicesettings_liftwrist_display_no_on,
|
R.xml.devicesettings_liftwrist_display_no_on,
|
||||||
|
|||||||
+2
-1
@@ -334,7 +334,8 @@ public class HuaweiCoordinator {
|
|||||||
// Other
|
// Other
|
||||||
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_find_phone);
|
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_find_phone);
|
||||||
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_disable_find_phone_with_dnd);
|
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_disable_find_phone_with_dnd);
|
||||||
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_allow_accept_reject_calls);
|
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_allow_accept_calls);
|
||||||
|
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_allow_reject_calls);
|
||||||
|
|
||||||
// Camera control
|
// Camera control
|
||||||
if (supportsCameraRemote())
|
if (supportsCameraRemote())
|
||||||
|
|||||||
+2
-2
@@ -40,7 +40,6 @@ public class PebbleNotification {
|
|||||||
|
|
||||||
private byte setColor(NotificationType notificationType) {
|
private byte setColor(NotificationType notificationType) {
|
||||||
return switch (notificationType) {
|
return switch (notificationType) {
|
||||||
case UNKNOWN -> PebbleColor.DarkCandyAppleRed;
|
|
||||||
case AMAZON -> PebbleColor.ChromeYellow;
|
case AMAZON -> PebbleColor.ChromeYellow;
|
||||||
case BBM -> PebbleColor.DarkGray;
|
case BBM -> PebbleColor.DarkGray;
|
||||||
case CONVERSATIONS -> PebbleColor.Inchworm;
|
case CONVERSATIONS -> PebbleColor.Inchworm;
|
||||||
@@ -64,12 +63,12 @@ public class PebbleNotification {
|
|||||||
case WECHAT -> PebbleColor.KellyGreen;
|
case WECHAT -> PebbleColor.KellyGreen;
|
||||||
case YAHOO_MAIL -> PebbleColor.Indigo;
|
case YAHOO_MAIL -> PebbleColor.Indigo;
|
||||||
case ELEMENT, ELEMENTX -> PebbleColor.Malachite;
|
case ELEMENT, ELEMENTX -> PebbleColor.Malachite;
|
||||||
|
default -> PebbleColor.DarkCandyAppleRed;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private int setIcon(NotificationType notificationType) {
|
private int setIcon(NotificationType notificationType) {
|
||||||
return switch (notificationType) {
|
return switch (notificationType) {
|
||||||
case UNKNOWN -> PebbleIconID.NOTIFICATION_GENERIC;
|
|
||||||
case AMAZON -> PebbleIconID.NOTIFICATION_AMAZON;
|
case AMAZON -> PebbleIconID.NOTIFICATION_AMAZON;
|
||||||
case BBM -> PebbleIconID.NOTIFICATION_BLACKBERRY_MESSENGER;
|
case BBM -> PebbleIconID.NOTIFICATION_BLACKBERRY_MESSENGER;
|
||||||
case CONVERSATIONS, HIPCHAT, RIOT, SIGNAL, WIRE, THREEMA, KONTALK,
|
case CONVERSATIONS, HIPCHAT, RIOT, SIGNAL, WIRE, THREEMA, KONTALK,
|
||||||
@@ -107,6 +106,7 @@ public class PebbleNotification {
|
|||||||
case WHATSAPP -> PebbleIconID.NOTIFICATION_WHATSAPP;
|
case WHATSAPP -> PebbleIconID.NOTIFICATION_WHATSAPP;
|
||||||
case YAHOO_MAIL -> PebbleIconID.NOTIFICATION_YAHOO_MAIL;
|
case YAHOO_MAIL -> PebbleIconID.NOTIFICATION_YAHOO_MAIL;
|
||||||
case COL_REMINDER -> PebbleIconID.NOTIFICATION_REMINDER;
|
case COL_REMINDER -> PebbleIconID.NOTIFICATION_REMINDER;
|
||||||
|
default -> PebbleIconID.NOTIFICATION_GENERIC;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
*.java
|
*.java
|
||||||
!Abstract*.java
|
!Abstract*.java
|
||||||
|
!GenericActivitySample.java
|
||||||
|
|||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
/* 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.entities;
|
||||||
|
|
||||||
|
public class GenericActivitySample extends AbstractActivitySample {
|
||||||
|
private int timestamp;
|
||||||
|
private long userId;
|
||||||
|
private long deviceId;
|
||||||
|
|
||||||
|
private int rawKind = NOT_MEASURED;
|
||||||
|
private int rawIntensity = NOT_MEASURED;
|
||||||
|
private int steps = NOT_MEASURED;
|
||||||
|
private int distanceCm = NOT_MEASURED;
|
||||||
|
private int activeCalories = NOT_MEASURED;
|
||||||
|
private int heartRate = NOT_MEASURED;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(final int timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUserId(final long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDeviceId() {
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDeviceId(final long deviceId) {
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRawKind() {
|
||||||
|
return rawKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRawKind(final int rawKind) {
|
||||||
|
this.rawKind = rawKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRawIntensity() {
|
||||||
|
return rawIntensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRawIntensity(final int rawIntensity) {
|
||||||
|
this.rawIntensity = rawIntensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSteps() {
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSteps(final int steps) {
|
||||||
|
this.steps = steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDistanceCm() {
|
||||||
|
return distanceCm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDistanceCm(final int distanceCm) {
|
||||||
|
this.distanceCm = distanceCm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getActiveCalories() {
|
||||||
|
return activeCalories;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActiveCalories(final int activeCalories) {
|
||||||
|
this.activeCalories = activeCalories;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeartRate() {
|
||||||
|
return heartRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeartRate(final int heartRate) {
|
||||||
|
this.heartRate = heartRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -192,6 +192,13 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
|
|||||||
|
|
||||||
// KTrip
|
// KTrip
|
||||||
put("org.kde.ktrip", NotificationType.TRANSIT);
|
put("org.kde.ktrip", NotificationType.TRANSIT);
|
||||||
|
|
||||||
|
// Others
|
||||||
|
put("com.vkontakte.android", NotificationType.VK);
|
||||||
|
put("com.tencent.mobileqq", NotificationType.QQ);
|
||||||
|
put("com.tumblr", NotificationType.TUMBLR);
|
||||||
|
put("com.pinterest", NotificationType.PINTEREST);
|
||||||
|
put("com.google.android.youtube", NotificationType.YOUTUBE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -17,9 +17,9 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||||
|
|
||||||
public class CannedMessagesSpec {
|
public class CannedMessagesSpec {
|
||||||
public static final byte TYPE_GENERIC = 0;
|
public static final int TYPE_GENERIC = 0;
|
||||||
public static final byte TYPE_REJECTEDCALLS = 1;
|
public static final int TYPE_REJECTEDCALLS = 1;
|
||||||
public static final byte TYPE_NEWSMS = 2;
|
public static final int TYPE_NEWSMS = 2;
|
||||||
|
|
||||||
public int type;
|
public int type;
|
||||||
public String[] cannedMessages;
|
public String[] cannedMessages;
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosmart.Gar
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosmart.GarminVivosmart5Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosmart.GarminVivosmart5Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosport.GarminVivosportCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches.vivosport.GarminVivosportCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.generic_headphones.GenericHeadphonesCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.generic_headphones.GenericHeadphonesCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.gloryfit.watches.DotnP66DCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.gloryfit.watches.OukitelBT103Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.gree.GreeAcCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.gree.GreeAcCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900.HamaFit6900DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.hama.fit6900.HamaFit6900DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.EXRIZUK8Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.EXRIZUK8Coordinator;
|
||||||
@@ -698,6 +700,8 @@ public enum DeviceType {
|
|||||||
OPPO_ENCO_AIR(OppoEncoAirCoordinator.class),
|
OPPO_ENCO_AIR(OppoEncoAirCoordinator.class),
|
||||||
OPPO_ENCO_AIR2(OppoEncoAir2Coordinator.class),
|
OPPO_ENCO_AIR2(OppoEncoAir2Coordinator.class),
|
||||||
OPPO_ENCO_BUDS2(OppoEncoBuds2Coordinator.class),
|
OPPO_ENCO_BUDS2(OppoEncoBuds2Coordinator.class),
|
||||||
|
OUKITEL_BT103(OukitelBT103Coordinator.class),
|
||||||
|
DOTN_P66D(DotnP66DCoordinator.class),
|
||||||
REALME_BUDS_T110(RealmeBudsT110Coordinator.class),
|
REALME_BUDS_T110(RealmeBudsT110Coordinator.class),
|
||||||
REALME_BUDS_T100(RealmeBudsT100Coordinator.class),
|
REALME_BUDS_T100(RealmeBudsT100Coordinator.class),
|
||||||
REALME_BUDS_T300(RealmeBudsT300Coordinator.class),
|
REALME_BUDS_T300(RealmeBudsT300Coordinator.class),
|
||||||
|
|||||||
@@ -54,7 +54,13 @@ public enum NotificationType {
|
|||||||
DELTACHAT,
|
DELTACHAT,
|
||||||
ELEMENT,
|
ELEMENT,
|
||||||
ELEMENTX,
|
ELEMENTX,
|
||||||
MOLLY;
|
MOLLY,
|
||||||
|
VK,
|
||||||
|
QQ,
|
||||||
|
TUMBLR,
|
||||||
|
PINTEREST,
|
||||||
|
YOUTUBE,
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+6
@@ -499,6 +499,8 @@ public class BleNamesResolver {
|
|||||||
mServices.put("02f00000-0000-0000-0000-00000000ffe0", "(Propr: Nothing CMF Data");
|
mServices.put("02f00000-0000-0000-0000-00000000ffe0", "(Propr: Nothing CMF Data");
|
||||||
mServices.put("02f00000-0000-0000-0000-00000000fe00", "(Propr: Nothing CMF Firmware");
|
mServices.put("02f00000-0000-0000-0000-00000000fe00", "(Propr: Nothing CMF Firmware");
|
||||||
mServices.put("77d4e67c-2fe2-2334-0d35-9ccd078f529c", "(Propr: Nothing CMF Shell");
|
mServices.put("77d4e67c-2fe2-2334-0d35-9ccd078f529c", "(Propr: Nothing CMF Shell");
|
||||||
|
mServices.put("000055ff-0000-1000-8000-00805f9b34fb", "(Propr: GloryFit Command");
|
||||||
|
mServices.put("000056ff-0000-1000-8000-00805f9b34fb", "(Propr: GloryFit Data");
|
||||||
mServices.put("9b012401-bc30-ce9a-e111-0f67e491abde", "(Propr: Garmin GFDI V0)");
|
mServices.put("9b012401-bc30-ce9a-e111-0f67e491abde", "(Propr: Garmin GFDI V0)");
|
||||||
mServices.put("6a4e2401-667b-11e3-949a-0800200c9a66", "(Propr: Garmin GFDI V1)");
|
mServices.put("6a4e2401-667b-11e3-949a-0800200c9a66", "(Propr: Garmin GFDI V1)");
|
||||||
mServices.put("6a4e2800-667b-11e3-949a-0800200c9a66", "(Propr: Garmin ML)");
|
mServices.put("6a4e2800-667b-11e3-949a-0800200c9a66", "(Propr: Garmin ML)");
|
||||||
@@ -1006,6 +1008,10 @@ public class BleNamesResolver {
|
|||||||
mCharacteristics.put("77d4ff02-2fe2-2334-0d35-9ccd078f529c", "(Propr: Nothing CMF Shell Read");
|
mCharacteristics.put("77d4ff02-2fe2-2334-0d35-9ccd078f529c", "(Propr: Nothing CMF Shell Read");
|
||||||
mCharacteristics.put("02f00000-0000-0000-0000-00000000ff01", "(Propr: Nothing CMF Firmware Write");
|
mCharacteristics.put("02f00000-0000-0000-0000-00000000ff01", "(Propr: Nothing CMF Firmware Write");
|
||||||
mCharacteristics.put("02f00000-0000-0000-0000-00000000ff02", "(Propr: Nothing CMF Firmware Read");
|
mCharacteristics.put("02f00000-0000-0000-0000-00000000ff02", "(Propr: Nothing CMF Firmware Read");
|
||||||
|
mCharacteristics.put("000033f1-0000-1000-8000-00805f9b34fb", "(Propr: GloryFit Command Write");
|
||||||
|
mCharacteristics.put("000033f2-0000-1000-8000-00805f9b34fb", "(Propr: GloryFit Command Read");
|
||||||
|
mCharacteristics.put("000034f1-0000-1000-8000-00805f9b34fb", "(Propr: GloryFit Data Write");
|
||||||
|
mCharacteristics.put("000034f2-0000-1000-8000-00805f9b34fb", "(Propr: GloryFit Data Read");
|
||||||
mCharacteristics.put("00010203-0405-0607-0809-0a0b0c0d2b12", "(Propr: Telink OTA Write)");
|
mCharacteristics.put("00010203-0405-0607-0809-0a0b0c0d2b12", "(Propr: Telink OTA Write)");
|
||||||
mCharacteristics.put("ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd TIME)");
|
mCharacteristics.put("ebe0ccb7-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd TIME)");
|
||||||
mCharacteristics.put("ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd BATTERY)");
|
mCharacteristics.put("ebe0ccc4-7a0a-4b0c-8a1a-6ff2997da3a6", "(Propr: Lywsd BATTERY)");
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/* 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.gloryfit
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R
|
||||||
|
|
||||||
|
enum class GloryFitFetchType(val descriptionRes: Int) {
|
||||||
|
STEPS(R.string.busy_task_fetch_steps),
|
||||||
|
HEART_RATE(R.string.busy_task_fetch_hr_data),
|
||||||
|
SPO2(R.string.busy_task_fetch_spo2_data),
|
||||||
|
SLEEP(R.string.busy_task_fetch_sleep_data),
|
||||||
|
;
|
||||||
|
}
|
||||||
+404
@@ -0,0 +1,404 @@
|
|||||||
|
/* 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.gloryfit
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.GenericHeartRateSampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.GenericSleepStageSampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.GenericSpo2SampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.gloryfit.GloryFitStepsSampleProvider
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericHeartRateSample
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSleepStageSample
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GenericSpo2Sample
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.GloryFitStepsSample
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.GregorianCalendar
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.Queue
|
||||||
|
|
||||||
|
class GloryFitFetcher(val mSupport: GloryFitSupport) {
|
||||||
|
companion object {
|
||||||
|
private val LOG: Logger = LoggerFactory.getLogger(GloryFitFetcher::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mFetchQueue: Queue<GloryFitFetchType> = LinkedList()
|
||||||
|
private var mCurrentFetch: GloryFitFetchType? = null
|
||||||
|
private var mCurrentSleepSession: Calendar? = null
|
||||||
|
private var mCurrentSessionAfterMidnight = false
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
mFetchQueue.clear()
|
||||||
|
mCurrentFetch = null
|
||||||
|
mCurrentSleepSession = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onFetchRecordedData(dataTypes: Int) {
|
||||||
|
val coordinator = mSupport.device.deviceCoordinator
|
||||||
|
|
||||||
|
if ((dataTypes and RecordedDataTypes.TYPE_ACTIVITY) != 0) {
|
||||||
|
mFetchQueue.add(GloryFitFetchType.STEPS)
|
||||||
|
mFetchQueue.add(GloryFitFetchType.HEART_RATE)
|
||||||
|
mFetchQueue.add(GloryFitFetchType.SLEEP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((dataTypes and RecordedDataTypes.TYPE_SPO2) != 0 && coordinator.supportsSpo2(mSupport.device)) {
|
||||||
|
mFetchQueue.add(GloryFitFetchType.SPO2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCurrentFetch == null) {
|
||||||
|
triggerNextFetch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun triggerNextFetch() {
|
||||||
|
val wasFetching = mCurrentFetch != null
|
||||||
|
mCurrentFetch = this.mFetchQueue.poll()
|
||||||
|
|
||||||
|
mCurrentFetch?.let {
|
||||||
|
LOG.debug("Fetching next: {}", it)
|
||||||
|
|
||||||
|
mSupport.device.setBusyTask(
|
||||||
|
it.descriptionRes,
|
||||||
|
mSupport.context
|
||||||
|
)
|
||||||
|
mSupport.device.sendDeviceUpdateIntent(mSupport.context)
|
||||||
|
|
||||||
|
sendFetchCommand(it)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasFetching) {
|
||||||
|
LOG.debug("All operations finished")
|
||||||
|
|
||||||
|
GB.updateTransferNotification(null, "", false, 100, mSupport.context)
|
||||||
|
GB.signalActivityDataFinish(mSupport.device)
|
||||||
|
|
||||||
|
if (mSupport.device.isBusy) {
|
||||||
|
mSupport.device.unsetBusyTask()
|
||||||
|
mSupport.device.sendDeviceUpdateIntent(mSupport.context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendFetchCommand(type: GloryFitFetchType) {
|
||||||
|
val builder = mSupport.createTransactionBuilder("fetch $type")
|
||||||
|
val characteristic = mSupport.getCharacteristic(GloryFitSupport.UUID_CHARACTERISTIC_GLORYFIT_CMD_WRITE)
|
||||||
|
val cmd: ByteArray
|
||||||
|
when (type) {
|
||||||
|
GloryFitFetchType.STEPS -> {
|
||||||
|
cmd = byteArrayOf(GloryFitSupport.CMD_STEPS, GloryFitSupport.FETCH_START)
|
||||||
|
}
|
||||||
|
|
||||||
|
GloryFitFetchType.HEART_RATE -> {
|
||||||
|
cmd = byteArrayOf(GloryFitSupport.CMD_HEART_RATE, GloryFitSupport.FETCH_START)
|
||||||
|
}
|
||||||
|
|
||||||
|
GloryFitFetchType.SPO2 -> {
|
||||||
|
cmd = byteArrayOf(GloryFitSupport.CMD_SPO2, GloryFitSupport.FETCH_START)
|
||||||
|
}
|
||||||
|
|
||||||
|
GloryFitFetchType.SLEEP -> {
|
||||||
|
cmd = byteArrayOf(GloryFitSupport.CMD_SLEEP_INFO, 0x01)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.write(characteristic, *cmd)
|
||||||
|
|
||||||
|
builder.queue(mSupport.queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleSleepInfo(value: ByteArray) {
|
||||||
|
when (value[1]) {
|
||||||
|
GloryFitSupport.SLEEP_INFO_DATE -> {
|
||||||
|
val buf = ByteBuffer.wrap(value).order(ByteOrder.BIG_ENDIAN)
|
||||||
|
buf.get(ByteArray(2)) // discard first 2 bytes
|
||||||
|
val timestamp = buf.getDate()
|
||||||
|
val numStages = buf.get().toInt() and 0xff
|
||||||
|
mCurrentSleepSession = timestamp
|
||||||
|
mCurrentSessionAfterMidnight = false
|
||||||
|
LOG.debug(
|
||||||
|
"Got sleep info date {}, expect {} stages",
|
||||||
|
DateTimeUtils.formatIso8601(timestamp.time),
|
||||||
|
numStages
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
GloryFitSupport.SLEEP_INFO_END -> {
|
||||||
|
LOG.debug("Got sleep info end")
|
||||||
|
mCurrentSleepSession = null
|
||||||
|
triggerNextFetch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleSleepStages(value: ByteArray) {
|
||||||
|
mCurrentSleepSession?.let {
|
||||||
|
LOG.debug("Got sleep stages at {}", DateTimeUtils.formatIso8601(it.time))
|
||||||
|
|
||||||
|
if ((value.size - 1) % 6 != 0) {
|
||||||
|
LOG.error("Unexpected sleep stages payload size {}", value.size)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val buf = ByteBuffer.wrap(value).order(ByteOrder.BIG_ENDIAN)
|
||||||
|
buf.get() // discard first byte
|
||||||
|
|
||||||
|
val samples: MutableList<GenericSleepStageSample?> = mutableListOf()
|
||||||
|
|
||||||
|
while (buf.position() < buf.limit()) {
|
||||||
|
val timestamp = GregorianCalendar.getInstance()
|
||||||
|
timestamp.timeInMillis = it.timeInMillis
|
||||||
|
|
||||||
|
val sample = GenericSleepStageSample()
|
||||||
|
|
||||||
|
val hour = buf.get().toInt()
|
||||||
|
val minute = buf.get().toInt()
|
||||||
|
val stage = buf.get().toInt()
|
||||||
|
buf.get() // ? 1
|
||||||
|
val duration = buf.getShort()
|
||||||
|
|
||||||
|
if (hour > 12) {
|
||||||
|
// assume times after noon correspond to the previous day
|
||||||
|
// unless they already come after noon the next day
|
||||||
|
// TODO is this right?
|
||||||
|
if (!mCurrentSessionAfterMidnight) {
|
||||||
|
timestamp.add(Calendar.DATE, -1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mCurrentSessionAfterMidnight = true
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp.set(Calendar.HOUR_OF_DAY, hour)
|
||||||
|
timestamp.set(Calendar.MINUTE, minute)
|
||||||
|
|
||||||
|
sample.timestamp = timestamp.timeInMillis
|
||||||
|
sample.stage = stage
|
||||||
|
sample.duration = duration.toInt()
|
||||||
|
|
||||||
|
LOG.debug("Sleep stage at {}: {} for {}", DateTimeUtils.formatIso8601(timestamp.time), stage, duration)
|
||||||
|
|
||||||
|
samples.add(sample)
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Persisting {} sleep stage samples", samples.size)
|
||||||
|
|
||||||
|
try {
|
||||||
|
GBApplication.acquireDB().use { handler ->
|
||||||
|
val session = handler.getDaoSession()
|
||||||
|
val sampleProvider = GenericSleepStageSampleProvider(mSupport.device, session)
|
||||||
|
|
||||||
|
sampleProvider.persistForDevice(mSupport.context, mSupport.device, samples)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
GB.toast(mSupport.context, "Error saving sleep session samples", Toast.LENGTH_LONG, GB.ERROR, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.error("Got sleep stages, but sleep session date is unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleSteps(value: ByteArray) {
|
||||||
|
when {
|
||||||
|
value.size == 3 && value[1] == GloryFitSupport.FETCH_END -> {
|
||||||
|
LOG.debug("Got steps fetch end")
|
||||||
|
triggerNextFetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
value.size == 18 -> {
|
||||||
|
val buf = ByteBuffer.wrap(value).order(ByteOrder.BIG_ENDIAN)
|
||||||
|
buf.get() // discard first bytes
|
||||||
|
val timestamp = buf.getDate()
|
||||||
|
timestamp.set(Calendar.HOUR_OF_DAY, buf.get().toInt() and 0xff)
|
||||||
|
|
||||||
|
val sample = GloryFitStepsSample()
|
||||||
|
sample.timestamp = timestamp.timeInMillis
|
||||||
|
sample.totalSteps = buf.getShort().toInt()
|
||||||
|
sample.runningStart = buf.get().toInt()
|
||||||
|
sample.runningEnd = buf.get().toInt()
|
||||||
|
buf.get() // unk
|
||||||
|
sample.runningSteps = buf.getShort().toInt()
|
||||||
|
sample.walkingStart = buf.get().toInt()
|
||||||
|
sample.walkingEnd = buf.get().toInt()
|
||||||
|
buf.get() // unk
|
||||||
|
sample.walkingSteps = buf.getShort().toInt()
|
||||||
|
|
||||||
|
LOG.debug("Steps {}: {}", DateTimeUtils.formatIso8601(timestamp.time), sample.totalSteps)
|
||||||
|
|
||||||
|
try {
|
||||||
|
GBApplication.acquireDB().use { handler ->
|
||||||
|
val session = handler.getDaoSession()
|
||||||
|
val sampleProvider = GloryFitStepsSampleProvider(mSupport.device, session)
|
||||||
|
|
||||||
|
sampleProvider.persistForDevice(
|
||||||
|
mSupport.context,
|
||||||
|
mSupport.device,
|
||||||
|
Collections.singletonList(sample)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
GB.toast(mSupport.context, "Error saving steps samples", Toast.LENGTH_LONG, GB.ERROR, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> LOG.warn("Unknown steps command {}", value.toHexString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleHeartRate(value: ByteArray): Boolean {
|
||||||
|
when {
|
||||||
|
value.size == 18 && value[1] == GloryFitSupport.FETCH_DATA -> {
|
||||||
|
// f707e90703083030315c2f2f2e2f2f353331
|
||||||
|
// 07:40 - 53
|
||||||
|
// 07:20 - 47
|
||||||
|
// 07:00 - 47
|
||||||
|
|
||||||
|
val buf = ByteBuffer.wrap(value).order(ByteOrder.BIG_ENDIAN)
|
||||||
|
buf.get() // discard first byte
|
||||||
|
val timestamp = buf.getDate()
|
||||||
|
timestamp.set(Calendar.HOUR_OF_DAY, buf.get().toInt() and 0xff)
|
||||||
|
|
||||||
|
timestamp.add(Calendar.MINUTE, -10 * (buf.limit() - buf.position()) + 10)
|
||||||
|
|
||||||
|
val samples: MutableList<GenericHeartRateSample?> = mutableListOf()
|
||||||
|
|
||||||
|
while (buf.position() < buf.limit()) {
|
||||||
|
val hr = buf.get().toInt() and 0xff
|
||||||
|
if (hr != 0xff && hr != 0) {
|
||||||
|
val sample = GenericHeartRateSample()
|
||||||
|
sample.timestamp = timestamp.timeInMillis
|
||||||
|
sample.heartRate = hr
|
||||||
|
samples.add(sample)
|
||||||
|
LOG.trace("HR {}: {}", DateTimeUtils.formatIso8601(timestamp.time), hr)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp.add(Calendar.MINUTE, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Persisting {} HR samples", samples.size)
|
||||||
|
|
||||||
|
try {
|
||||||
|
GBApplication.acquireDB().use { handler ->
|
||||||
|
val session = handler.getDaoSession()
|
||||||
|
val sampleProvider = GenericHeartRateSampleProvider(mSupport.device, session)
|
||||||
|
|
||||||
|
sampleProvider.persistForDevice(mSupport.context, mSupport.device, samples)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
GB.toast(mSupport.context, "Error saving hr samples", Toast.LENGTH_LONG, GB.ERROR, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value.size == 3 && value[1] == GloryFitSupport.FETCH_END -> {
|
||||||
|
LOG.debug("Got hr fetch end")
|
||||||
|
triggerNextFetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleSpO2(value: ByteArray): Boolean {
|
||||||
|
when (value[2]) {
|
||||||
|
GloryFitSupport.FETCH_DATA -> {
|
||||||
|
// 34fa07e907030400ffff60ffff61ffff61ffff60
|
||||||
|
// Data in blocks of 10 minutes
|
||||||
|
// 04:00 - 96, 03:50 N/A
|
||||||
|
// 03:30 - 97
|
||||||
|
// 03:00 - 97
|
||||||
|
// 02:30 - 96
|
||||||
|
|
||||||
|
if (value.size != 20) {
|
||||||
|
LOG.error("Unexpected spo2 data length {}", value.size)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val buf = ByteBuffer.wrap(value).order(ByteOrder.BIG_ENDIAN)
|
||||||
|
buf.get(ByteArray(2)) // discard first 2 bytes
|
||||||
|
val timestamp = buf.getDate()
|
||||||
|
timestamp.set(Calendar.HOUR_OF_DAY, buf.get().toInt() and 0xff)
|
||||||
|
timestamp.set(Calendar.MINUTE, buf.get().toInt() and 0xff)
|
||||||
|
|
||||||
|
timestamp.add(Calendar.MINUTE, -10 * (buf.limit() - buf.position()) + 10)
|
||||||
|
|
||||||
|
val samples: MutableList<GenericSpo2Sample?> = mutableListOf()
|
||||||
|
|
||||||
|
while (buf.position() < buf.limit()) {
|
||||||
|
val spo2 = buf.get().toInt() and 0xff
|
||||||
|
if (spo2 != 0xff && spo2 != 0) {
|
||||||
|
val sample = GenericSpo2Sample()
|
||||||
|
sample.timestamp = timestamp.timeInMillis
|
||||||
|
sample.spo2 = spo2
|
||||||
|
samples.add(sample)
|
||||||
|
LOG.trace("SpO2 {}: {}", DateTimeUtils.formatIso8601(timestamp.time), spo2)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp.add(Calendar.MINUTE, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Persisting {} SpO2 samples", samples.size)
|
||||||
|
|
||||||
|
try {
|
||||||
|
GBApplication.acquireDB().use { handler ->
|
||||||
|
val session = handler.getDaoSession()
|
||||||
|
val sampleProvider = GenericSpo2SampleProvider(mSupport.device, session)
|
||||||
|
|
||||||
|
sampleProvider.persistForDevice(mSupport.context, mSupport.device, samples)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
GB.toast(mSupport.context, "Error saving SpO2 samples", Toast.LENGTH_LONG, GB.ERROR, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GloryFitSupport.FETCH_END -> {
|
||||||
|
LOG.debug("Got SpO2 fetch end")
|
||||||
|
triggerNextFetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteBuffer.getDate(): Calendar {
|
||||||
|
val timestamp = GregorianCalendar.getInstance()
|
||||||
|
|
||||||
|
timestamp.set(Calendar.YEAR, getShort().toInt() and 0xffff)
|
||||||
|
timestamp.set(Calendar.MONTH, (get().toInt() and 0xff) - 1)
|
||||||
|
timestamp.set(Calendar.DATE, get().toInt() and 0xff)
|
||||||
|
timestamp.set(Calendar.HOUR_OF_DAY, 0)
|
||||||
|
timestamp.set(Calendar.MINUTE, 0)
|
||||||
|
timestamp.set(Calendar.SECOND, 0)
|
||||||
|
timestamp.set(Calendar.MILLISECOND, 0)
|
||||||
|
|
||||||
|
return timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -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.gloryfit
|
||||||
|
|
||||||
|
enum class GloryFitLanguage(val locale: String, val code: Byte) {
|
||||||
|
CHINESE_SIMPLIFIED("zh_CN", 0x01),
|
||||||
|
CHINESE_TRADITIONAL("zh_TW", 0x17),
|
||||||
|
ENGLISH("en", 0x02),
|
||||||
|
KOREAN("ko", 0x03),
|
||||||
|
JAPANESE("ja", 0x04),
|
||||||
|
GERMAN("de", 0x05),
|
||||||
|
SPANISH("es", 0x06),
|
||||||
|
FRENCH("fr", 0x07),
|
||||||
|
ITALIAN("it", 0x08),
|
||||||
|
PORTUGUESE("pt", 0x09),
|
||||||
|
ARABIC("ar", 0x0a),
|
||||||
|
POLISH("pl", 0x0d),
|
||||||
|
RUSSIAN("ru", 0x0e),
|
||||||
|
DUTCH("nl", 0x0f),
|
||||||
|
TURKISH("tr", 0x10),
|
||||||
|
BENGALI("bn", 0x11),
|
||||||
|
INDONESIAN("id", 0x13),
|
||||||
|
CZECH("cs", 0x16),
|
||||||
|
HEBREW("he", 0x18),
|
||||||
|
THAI("th", 0x15),
|
||||||
|
PERSIAN("fa", 0x28),
|
||||||
|
VIETNAMESE("vi", 0x63),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromLocale(locale: String): GloryFitLanguage? {
|
||||||
|
return entries.find { language -> language.locale == locale }
|
||||||
|
// Fallback - attempt to find the next closest one
|
||||||
|
?: entries.find { language -> language.locale.substring(0, 2) == locale.substring(0, 2) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
/* 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.gloryfit
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType
|
||||||
|
|
||||||
|
enum class GloryFitNotificationType(val code: Byte) {
|
||||||
|
CALL(0),
|
||||||
|
QQ(1),
|
||||||
|
WECHAT(2),
|
||||||
|
SMS(3),
|
||||||
|
UNKNOWN_APP(4),
|
||||||
|
FACEBOOK(5),
|
||||||
|
TWITTER(6),
|
||||||
|
WHATSAPP(7),
|
||||||
|
SKYPE(8),
|
||||||
|
FACEBOOK_MESSENGER(9),
|
||||||
|
HANGOUTS(10),
|
||||||
|
LINE(11),
|
||||||
|
LINKEDIN(12),
|
||||||
|
INSTAGRAM(13),
|
||||||
|
VIBER(14),
|
||||||
|
KAKAO_TALK(15),
|
||||||
|
VK(16),
|
||||||
|
SNAPCHAT(17),
|
||||||
|
GOOGLE_PLUS(18),
|
||||||
|
EMAIL(19),
|
||||||
|
UNK_BLUE_RED_DOT(20),
|
||||||
|
TUMBLR(21),
|
||||||
|
PINTEREST(22),
|
||||||
|
YOUTUBE(23),
|
||||||
|
TELEGRAM(24),
|
||||||
|
NO_ICON(25),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromNotificationType(type: NotificationType): GloryFitNotificationType {
|
||||||
|
when (type) {
|
||||||
|
NotificationType.CONVERSATIONS, NotificationType.RIOT, NotificationType.HIPCHAT, NotificationType.KONTALK,
|
||||||
|
NotificationType.ANTOX, NotificationType.GENERIC_SMS, NotificationType.WECHAT,
|
||||||
|
NotificationType.SIGNAL-> return WECHAT
|
||||||
|
|
||||||
|
NotificationType.GENERIC_EMAIL, NotificationType.GMAIL, NotificationType.YAHOO_MAIL,
|
||||||
|
NotificationType.OUTLOOK -> return EMAIL
|
||||||
|
|
||||||
|
NotificationType.FACEBOOK -> return FACEBOOK
|
||||||
|
NotificationType.FACEBOOK_MESSENGER -> return FACEBOOK_MESSENGER
|
||||||
|
NotificationType.GOOGLE_HANGOUTS, NotificationType.GOOGLE_MESSENGER -> return HANGOUTS
|
||||||
|
NotificationType.INSTAGRAM, NotificationType.GOOGLE_PHOTOS -> return INSTAGRAM
|
||||||
|
NotificationType.KAKAO_TALK -> return KAKAO_TALK
|
||||||
|
NotificationType.LINE -> return LINE
|
||||||
|
NotificationType.TWITTER -> return TWITTER
|
||||||
|
NotificationType.SKYPE -> return SKYPE
|
||||||
|
NotificationType.SNAPCHAT -> return SNAPCHAT
|
||||||
|
NotificationType.TELEGRAM -> return TELEGRAM
|
||||||
|
NotificationType.VIBER, NotificationType.DISCORD -> return VIBER
|
||||||
|
NotificationType.WHATSAPP -> return WHATSAPP
|
||||||
|
NotificationType.VK -> return VK
|
||||||
|
NotificationType.QQ -> return QQ
|
||||||
|
NotificationType.TUMBLR -> return TUMBLR
|
||||||
|
NotificationType.PINTEREST -> return PINTEREST
|
||||||
|
NotificationType.YOUTUBE -> return YOUTUBE
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
when (type.genericType) {
|
||||||
|
"generic_email" -> return EMAIL
|
||||||
|
"generic_chat" -> return WECHAT
|
||||||
|
}
|
||||||
|
return UNKNOWN_APP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1320
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -394,7 +394,7 @@ public class AsynchronousResponse {
|
|||||||
callControlEvent.event = GBDeviceEventCallControl.Event.REJECT;
|
callControlEvent.event = GBDeviceEventCallControl.Event.REJECT;
|
||||||
LOG.info("Rejected call");
|
LOG.info("Rejected call");
|
||||||
|
|
||||||
if (!prefs.getBoolean("enable_call_reject", true)) {
|
if (!prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_ENABLE_CALL_REJECT, true)) {
|
||||||
LOG.info("Disabled rejecting calls, ignoring");
|
LOG.info("Disabled rejecting calls, ignoring");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -223,4 +224,25 @@ public class StringUtils {
|
|||||||
}
|
}
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] truncateUtf16BE(final String str, final int maxBytes) {
|
||||||
|
final byte[] utf16Bytes = str.getBytes(StandardCharsets.UTF_16BE);
|
||||||
|
|
||||||
|
// UTF-16 code units are 2 bytes, so truncate at even boundary
|
||||||
|
int limit = Math.min(maxBytes, utf16Bytes.length);
|
||||||
|
if (limit % 2 != 0) {
|
||||||
|
limit -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for surrogate pair at the cut point
|
||||||
|
int highByte = utf16Bytes[limit - 2] & 0xFF;
|
||||||
|
int lowByte = utf16Bytes[limit - 1] & 0xFF;
|
||||||
|
int lastCodeUnit = (highByte << 8) | lowByte;
|
||||||
|
// If it's a high surrogate (0xD800 to 0xDBFF), remove 2 more bytes to drop the full pair
|
||||||
|
if (lastCodeUnit >= 0xD800 && lastCodeUnit <= 0xDBFF) {
|
||||||
|
limit -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.copyOfRange(utf16Bytes, 0, limit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -25,6 +25,8 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
||||||
@@ -72,6 +74,7 @@ public class DevicePrefs extends Prefs {
|
|||||||
return getBoolean("fetch_unknown_files", false);
|
return getBoolean("fetch_unknown_files", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public String getTimeFormat() {
|
public String getTimeFormat() {
|
||||||
String timeFormat = getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO);
|
String timeFormat = getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO);
|
||||||
if (DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO.equals(timeFormat)) {
|
if (DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_AUTO.equals(timeFormat)) {
|
||||||
@@ -136,4 +139,12 @@ public class DevicePrefs extends Prefs {
|
|||||||
// either set to default, unknown option selected, or has not been set
|
// either set to default, unknown option selected, or has not been set
|
||||||
return DeviceCoordinator.ConnectionType.BOTH;
|
return DeviceCoordinator.ConnectionType.BOTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getHeartRateHighThreshold() {
|
||||||
|
return getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_HIGH_THRESHOLD, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeartRateLowThreshold() {
|
||||||
|
return getInt(DeviceSettingsPreferenceConst.PREF_HEARTRATE_ALERT_LOW_THRESHOLD, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
tools:layout_editor_absoluteY="81dp">
|
tools:layout_editor_absoluteY="81dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView2"
|
android:id="@+id/textViewLedColorTitle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
@@ -20,16 +20,16 @@
|
|||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinnerLedType"
|
android:id="@+id/spinnerLedType"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="40dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/textView2" />
|
app:layout_constraintTop_toBottomOf="@+id/textViewLedColorTitle" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView3"
|
android:id="@+id/textViewVibration"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
@@ -41,18 +41,18 @@
|
|||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinnerVibraType"
|
android:id="@+id/spinnerVibraType"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="40dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/textView3" />
|
app:layout_constraintTop_toBottomOf="@+id/textViewVibration" />
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinnerVibraCount"
|
android:id="@+id/spinnerVibraCount"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="40dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
|
|||||||
@@ -2706,6 +2706,28 @@
|
|||||||
<item name="fi_FI">@string/finnish</item>
|
<item name="fi_FI">@string/finnish</item>
|
||||||
<item name="hu_HU">@string/hungarian</item>
|
<item name="hu_HU">@string/hungarian</item>
|
||||||
<item name="ms_MY">@string/bahasa_melayu</item>
|
<item name="ms_MY">@string/bahasa_melayu</item>
|
||||||
|
<item name="fa_IR">@string/persian</item>
|
||||||
|
<!-- simplified versions, without a country -->
|
||||||
|
<item name="en">@string/english</item>
|
||||||
|
<item name="ko">@string/korean</item>
|
||||||
|
<item name="ja">@string/japanese</item>
|
||||||
|
<item name="de">@string/german</item>
|
||||||
|
<item name="es">@string/spanish</item>
|
||||||
|
<item name="fr">@string/french</item>
|
||||||
|
<item name="it">@string/italian</item>
|
||||||
|
<item name="pt">@string/portuguese</item>
|
||||||
|
<item name="ar">@string/arabic</item>
|
||||||
|
<item name="pl">@string/polish</item>
|
||||||
|
<item name="ru">@string/russian</item>
|
||||||
|
<item name="nl">@string/dutch</item>
|
||||||
|
<item name="tr">@string/turkish</item>
|
||||||
|
<item name="bn">@string/bengali</item>
|
||||||
|
<item name="id">@string/indonesian</item>
|
||||||
|
<item name="cs">@string/czech</item>
|
||||||
|
<item name="he">@string/hebrew</item>
|
||||||
|
<item name="th">@string/thai</item>
|
||||||
|
<item name="fa">@string/persian</item>
|
||||||
|
<item name="vi">@string/vietnamese</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="pref_language_all_values">
|
<string-array name="pref_language_all_values">
|
||||||
@@ -2749,6 +2771,28 @@
|
|||||||
<item>fi_FI</item>
|
<item>fi_FI</item>
|
||||||
<item>hu_HU</item>
|
<item>hu_HU</item>
|
||||||
<item>ms_MY</item>
|
<item>ms_MY</item>
|
||||||
|
<item>fa_IR</item>
|
||||||
|
<!-- simplified versions, without a country -->
|
||||||
|
<item>en</item>
|
||||||
|
<item>ko</item>
|
||||||
|
<item>ja</item>
|
||||||
|
<item>de</item>
|
||||||
|
<item>es</item>
|
||||||
|
<item>fr</item>
|
||||||
|
<item>it</item>
|
||||||
|
<item>pt</item>
|
||||||
|
<item>ar</item>
|
||||||
|
<item>pl</item>
|
||||||
|
<item>ru</item>
|
||||||
|
<item>nl</item>
|
||||||
|
<item>tr</item>
|
||||||
|
<item>bn</item>
|
||||||
|
<item>id</item>
|
||||||
|
<item>cs</item>
|
||||||
|
<item>he</item>
|
||||||
|
<item>th</item>
|
||||||
|
<item>fa</item>
|
||||||
|
<item>vi</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="pref_amazfitbip_language">
|
<string-array name="pref_amazfitbip_language">
|
||||||
@@ -2847,6 +2891,26 @@
|
|||||||
<item>0</item>
|
<item>0</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="prefs_spo2_measurement_interval">
|
||||||
|
<item name="600">@string/interval_ten_minutes</item>
|
||||||
|
<item name="1800">@string/interval_thirty_minutes</item>
|
||||||
|
<item name="3600">@string/interval_1_hour</item>
|
||||||
|
<item name="7200">@string/interval_2_hour</item>
|
||||||
|
<item name="10800">@string/interval_3_hour</item>
|
||||||
|
<item name="14400">@string/interval_4_hour</item>
|
||||||
|
<item name="21600">@string/interval_6_hour</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="prefs_spo2_measurement_interval_values">
|
||||||
|
<item>600</item>
|
||||||
|
<item>1800</item>
|
||||||
|
<item>3600</item>
|
||||||
|
<item>7200</item>
|
||||||
|
<item>10800</item>
|
||||||
|
<item>14400</item>
|
||||||
|
<item>21600</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string-array name="prefs_miband_heartrate_alert_threshold">
|
<string-array name="prefs_miband_heartrate_alert_threshold">
|
||||||
<item name="100">@string/heartrate_bpm_100</item>
|
<item name="100">@string/heartrate_bpm_100</item>
|
||||||
<item name="105">@string/heartrate_bpm_105</item>
|
<item name="105">@string/heartrate_bpm_105</item>
|
||||||
|
|||||||
@@ -265,6 +265,7 @@
|
|||||||
<string name="pref_title_notification_wake_on_open">Auto wake and unlock</string>
|
<string name="pref_title_notification_wake_on_open">Auto wake and unlock</string>
|
||||||
<string name="pref_summary_notification_wake_on_open">Wake and unlock the Android device when the gadget sends a OPEN response back. Needs to be in a trusted state.</string>
|
<string name="pref_summary_notification_wake_on_open">Wake and unlock the Android device when the gadget sends a OPEN response back. Needs to be in a trusted state.</string>
|
||||||
<string name="pref_summary_receive_calls_watch">Perform and receive calls directly on the watch</string>
|
<string name="pref_summary_receive_calls_watch">Perform and receive calls directly on the watch</string>
|
||||||
|
<string name="phone_calls">Phone calls</string>
|
||||||
<string name="bluetooth_calls">Bluetooth calls</string>
|
<string name="bluetooth_calls">Bluetooth calls</string>
|
||||||
<string name="bluetooth_calls_pairing">Bluetooth calls pairing</string>
|
<string name="bluetooth_calls_pairing">Bluetooth calls pairing</string>
|
||||||
<string name="bluetooth_calls_settings">Bluetooth calls settings</string>
|
<string name="bluetooth_calls_settings">Bluetooth calls settings</string>
|
||||||
@@ -823,6 +824,11 @@
|
|||||||
<string name="spo2_perc_90">90%</string>
|
<string name="spo2_perc_90">90%</string>
|
||||||
<string name="spo2_off">Off</string>
|
<string name="spo2_off">Off</string>
|
||||||
<string name="interval_one_hour">once an hour</string>
|
<string name="interval_one_hour">once an hour</string>
|
||||||
|
<string name="interval_1_hour">every 1 hour</string>
|
||||||
|
<string name="interval_2_hour">every 2 hours</string>
|
||||||
|
<string name="interval_3_hour">every 3 hours</string>
|
||||||
|
<string name="interval_4_hour">every 4 hours</string>
|
||||||
|
<string name="interval_6_hour">every 6 hours</string>
|
||||||
<string name="stats_title">Speed zones</string>
|
<string name="stats_title">Speed zones</string>
|
||||||
<string name="stats_x_axis_label">Total minutes</string>
|
<string name="stats_x_axis_label">Total minutes</string>
|
||||||
<string name="stats_y_axis_label">Steps per minute</string>
|
<string name="stats_y_axis_label">Steps per minute</string>
|
||||||
@@ -1144,6 +1150,7 @@
|
|||||||
<string name="mi2_prefs_inactivity_warnings_summary">The band will vibrate when you have been inactive for a while</string>
|
<string name="mi2_prefs_inactivity_warnings_summary">The band will vibrate when you have been inactive for a while</string>
|
||||||
<string name="mi2_prefs_inactivity_warnings_threshold">Inactivity threshold (in minutes)</string>
|
<string name="mi2_prefs_inactivity_warnings_threshold">Inactivity threshold (in minutes)</string>
|
||||||
<string name="mi2_prefs_inactivity_warnings_dnd_summary">Disable inactivity warnings for a time interval</string>
|
<string name="mi2_prefs_inactivity_warnings_dnd_summary">Disable inactivity warnings for a time interval</string>
|
||||||
|
<string name="mi2_prefs_inactivity_warnings_dnd_lunch_break_summary">Disable inactivity warnings during the lunch break (12:00 - 14:00)</string>
|
||||||
<string name="hydration_dnd_summary">Disable hydration warnings for a time interval</string>
|
<string name="hydration_dnd_summary">Disable hydration warnings for a time interval</string>
|
||||||
<string name="mi2_prefs_heart_rate_monitoring">Heart Rate Monitoring</string>
|
<string name="mi2_prefs_heart_rate_monitoring">Heart Rate Monitoring</string>
|
||||||
<string name="mi2_prefs_heart_rate_monitoring_summary">Configure heart rate monitoring</string>
|
<string name="mi2_prefs_heart_rate_monitoring_summary">Configure heart rate monitoring</string>
|
||||||
@@ -1193,8 +1200,8 @@
|
|||||||
<string name="lefun_prefs_interface_language_title">Interface language</string>
|
<string name="lefun_prefs_interface_language_title">Interface language</string>
|
||||||
<string name="automatic">Automatic</string>
|
<string name="automatic">Automatic</string>
|
||||||
<string name="manual">Manual</string>
|
<string name="manual">Manual</string>
|
||||||
<string name="simplified_chinese">Simplified Chinese</string>
|
<string name="simplified_chinese">Chinese (Simplified)</string>
|
||||||
<string name="traditional_chinese">Traditional Chinese</string>
|
<string name="traditional_chinese">Chinese (Traditional)</string>
|
||||||
<string name="english">English</string>
|
<string name="english">English</string>
|
||||||
<string name="english_au">English (Australia)</string>
|
<string name="english_au">English (Australia)</string>
|
||||||
<string name="english_ca">English (Canada)</string>
|
<string name="english_ca">English (Canada)</string>
|
||||||
@@ -3276,6 +3283,8 @@
|
|||||||
<string name="pref_enable_call_accept_summary">Enable accepting calls from the device</string>
|
<string name="pref_enable_call_accept_summary">Enable accepting calls from the device</string>
|
||||||
<string name="pref_enable_call_reject">Enable rejecting calls</string>
|
<string name="pref_enable_call_reject">Enable rejecting calls</string>
|
||||||
<string name="pref_enable_call_reject_summary">Enable rejecting calls from the device</string>
|
<string name="pref_enable_call_reject_summary">Enable rejecting calls from the device</string>
|
||||||
|
<string name="pref_enable_sms_quick_reply_title">Enable SMS replies</string>
|
||||||
|
<string name="pref_enable_sms_quick_reply_summary">Allow rejecting calls with an SMS reply</string>
|
||||||
|
|
||||||
<string name="pref_disable_find_phone_with_dnd">Disable find my phone when do not disturb is active</string>
|
<string name="pref_disable_find_phone_with_dnd">Disable find my phone when do not disturb is active</string>
|
||||||
|
|
||||||
@@ -4121,4 +4130,8 @@
|
|||||||
<string name="activity_print_label_print_size">Print size (px): %1$d x %2$d</string>
|
<string name="activity_print_label_print_size">Print size (px): %1$d x %2$d</string>
|
||||||
<string name="service_notification_collector_service_title">Gadgetbridge Notification Monitor</string>
|
<string name="service_notification_collector_service_title">Gadgetbridge Notification Monitor</string>
|
||||||
<string name="service_notification_collector_service_text">Monitoring notifications in background</string>
|
<string name="service_notification_collector_service_text">Monitoring notifications in background</string>
|
||||||
|
<string name="devicetype_oukitel_bt103">Oukitel BT103</string>
|
||||||
|
<string name="devicetype_dotn_p66d">Dotn P66D</string>
|
||||||
|
<string name="pref_title_time_interval">Time interval</string>
|
||||||
|
<string name="pref_summary_time_interval">Restrict to specific intervals</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
-7
@@ -7,11 +7,4 @@
|
|||||||
android:layout="@layout/preference_checkbox"
|
android:layout="@layout/preference_checkbox"
|
||||||
android:title="@string/pref_enable_call_accept"
|
android:title="@string/pref_enable_call_accept"
|
||||||
android:summary="@string/pref_enable_call_accept_summary" />
|
android:summary="@string/pref_enable_call_accept_summary" />
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:defaultValue="true"
|
|
||||||
android:icon="@drawable/ic_notifications"
|
|
||||||
android:key="enable_call_reject"
|
|
||||||
android:layout="@layout/preference_checkbox"
|
|
||||||
android:title="@string/pref_enable_call_reject"
|
|
||||||
android:summary="@string/pref_enable_call_reject_summary" />
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:icon="@drawable/ic_notifications"
|
||||||
|
android:key="enable_call_reject"
|
||||||
|
android:layout="@layout/preference_checkbox"
|
||||||
|
android:title="@string/pref_enable_call_reject"
|
||||||
|
android:summary="@string/pref_enable_call_reject_summary" />
|
||||||
|
</PreferenceScreen>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="pref_header_phone_calls"
|
||||||
|
android:title="@string/phone_calls" />
|
||||||
|
</androidx.preference.PreferenceScreen>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="0"
|
||||||
|
android:entries="@array/prefs_miband_heartrate_high_alert_threshold_with_off"
|
||||||
|
android:entryValues="@array/prefs_miband_heartrate_high_alert_threshold_with_off_values"
|
||||||
|
android:icon="@drawable/ic_heartrate"
|
||||||
|
android:key="heartrate_alert_threshold"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/prefs_heartrate_alert_high_threshold" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="0"
|
||||||
|
android:entries="@array/prefs_miband_heartrate_low_alert_threshold"
|
||||||
|
android:entryValues="@array/prefs_miband_heartrate_low_alert_threshold_values"
|
||||||
|
android:icon="@drawable/ic_heartrate"
|
||||||
|
android:key="heartrate_alert_low_threshold"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/prefs_heartrate_alert_low_threshold" />
|
||||||
|
</androidx.preference.PreferenceScreen>
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
|
android:icon="@drawable/ic_chair"
|
||||||
android:key="inactivity_warnings_enable"
|
android:key="inactivity_warnings_enable"
|
||||||
android:layout="@layout/preference_checkbox"
|
android:layout="@layout/preference_checkbox"
|
||||||
android:title="@string/mi2_prefs_inactivity_warnings"
|
android:title="@string/mi2_prefs_inactivity_warnings"
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:dependency="inactivity_warnings_enable"
|
android:dependency="inactivity_warnings_enable"
|
||||||
|
android:icon="@drawable/ic_dnd"
|
||||||
android:key="inactivity_warnings_dnd"
|
android:key="inactivity_warnings_dnd"
|
||||||
android:layout="@layout/preference_checkbox"
|
android:layout="@layout/preference_checkbox"
|
||||||
android:summary="@string/mi2_prefs_inactivity_warnings_dnd_summary"
|
android:summary="@string/mi2_prefs_inactivity_warnings_dnd_summary"
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
android:summary="@string/mi2_prefs_inactivity_warnings_summary"
|
android:summary="@string/mi2_prefs_inactivity_warnings_summary"
|
||||||
android:title="@string/mi2_prefs_inactivity_warnings">
|
android:title="@string/mi2_prefs_inactivity_warnings">
|
||||||
|
|
||||||
<!-- workaround for missing toolbar -->
|
|
||||||
<PreferenceCategory android:title="@string/mi2_prefs_inactivity_warnings_summary" />
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="inactivity_warnings_enable"
|
android:key="inactivity_warnings_enable"
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<Preference
|
||||||
|
android:title="@string/prefs_wena3_notification_per_app_settings_title"
|
||||||
|
android:icon="@drawable/ic_widgets"
|
||||||
|
android:key="pref_per_app_notification_settings">
|
||||||
|
</Preference>
|
||||||
|
</PreferenceScreen>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:icon="@drawable/ic_reply"
|
||||||
|
android:key="enable_sms_quick_reply"
|
||||||
|
android:layout="@layout/preference_checkbox"
|
||||||
|
android:summary="@string/pref_enable_sms_quick_reply_summary"
|
||||||
|
android:title="@string/pref_enable_sms_quick_reply_title" />
|
||||||
|
</PreferenceScreen>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<PreferenceScreen
|
||||||
|
android:icon="@drawable/ic_spo2"
|
||||||
|
android:key="pref_screen_spo2"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/pref_header_spo2">
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:icon="@drawable/ic_spo2"
|
||||||
|
android:key="spo2_all_day_monitoring_enabled"
|
||||||
|
android:layout="@layout/preference_checkbox"
|
||||||
|
android:summary="@string/prefs_spo2_monitoring_description"
|
||||||
|
android:title="@string/prefs_spo2_monitoring_title" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="600"
|
||||||
|
android:dependency="spo2_all_day_monitoring_enabled"
|
||||||
|
android:entries="@array/prefs_spo2_measurement_interval"
|
||||||
|
android:entryValues="@array/prefs_spo2_measurement_interval_values"
|
||||||
|
android:key="spo2_measurement_interval"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/pref_title_time_interval" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:dependency="spo2_all_day_monitoring_enabled"
|
||||||
|
android:key="spo2_measurement_time"
|
||||||
|
android:layout="@layout/preference_checkbox"
|
||||||
|
android:summary="@string/pref_summary_time_interval"
|
||||||
|
android:title="@string/pref_title_time_interval" />
|
||||||
|
|
||||||
|
<nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
|
||||||
|
android:defaultValue="06:00"
|
||||||
|
android:dependency="spo2_measurement_time"
|
||||||
|
android:key="spo2_measurement_start"
|
||||||
|
android:title="@string/mi2_prefs_do_not_disturb_start" />
|
||||||
|
|
||||||
|
<nodomain.freeyourgadget.gadgetbridge.util.XTimePreference
|
||||||
|
android:defaultValue="22:00"
|
||||||
|
android:dependency="spo2_measurement_time"
|
||||||
|
android:key="spo2_measurement_end"
|
||||||
|
android:title="@string/mi2_prefs_do_not_disturb_end" />
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
||||||
|
</androidx.preference.PreferenceScreen>
|
||||||
@@ -49,7 +49,6 @@
|
|||||||
android:title="@string/prefs_wena3_notification_per_app_settings_title"
|
android:title="@string/prefs_wena3_notification_per_app_settings_title"
|
||||||
android:icon="@drawable/ic_widgets"
|
android:icon="@drawable/ic_widgets"
|
||||||
android:key="pref_per_app_notification_settings">
|
android:key="pref_per_app_notification_settings">
|
||||||
<intent android:action=".SonyWena3PerAppNotificationSettingsActivity"/>
|
|
||||||
</Preference>
|
</Preference>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|||||||
Reference in New Issue
Block a user