diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
index d5c7aa51b..8cabb6c52 100644
--- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
+++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
@@ -43,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
- final Schema schema = new Schema(51, MAIN_PACKAGE + ".entities");
+ final Schema schema = new Schema(59, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@@ -94,12 +94,20 @@ public class GBDaoGenerator {
addHybridHRActivitySample(schema, user, device);
addVivomoveHrActivitySample(schema, user, device);
addGarminFitFile(schema, user, device);
+ addWena3EnergySample(schema, user, device);
+ addWena3BehaviorSample(schema, user, device);
+ addWena3CaloriesSample(schema, user, device);
+ addWena3ActivitySample(schema, user, device);
+ addWena3HeartRateSample(schema, user, device);
+ addWena3Vo2Sample(schema, user, device);
+ addWena3StressSample(schema, user, device);
addCalendarSyncState(schema, device);
addAlarms(schema, user, device);
addReminders(schema, user, device);
addWorldClocks(schema, user, device);
addContacts(schema, user, device);
+ addAppSpecificNotificationSettings(schema, device);
Entity notificationFilter = addNotificationFilters(schema);
@@ -863,4 +871,71 @@ public class GBDaoGenerator {
activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE);
return activitySample;
}
+
+ private static Entity addWena3BehaviorSample(Schema schema, Entity user, Entity device) {
+ Entity activitySample = addEntity(schema, "Wena3BehaviorSample");
+ addCommonTimeSampleProperties("AbstractTimeSample", activitySample, user, device);
+
+ activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull();
+ activitySample.addLongProperty(TIMESTAMP_FROM).notNull();
+ activitySample.addLongProperty(TIMESTAMP_TO).notNull();
+ return activitySample;
+ }
+
+ private static Entity addWena3Vo2Sample(Schema schema, Entity user, Entity device) {
+ Entity activitySample = addEntity(schema, "Wena3Vo2Sample");
+ addCommonTimeSampleProperties("AbstractTimeSample", activitySample, user, device);
+ activitySample.addIntProperty("vo2").notNull();
+ activitySample.addIntProperty("datapoint").notNull().primaryKey();
+ return activitySample;
+ }
+
+ private static Entity addWena3StressSample(Schema schema, Entity user, Entity device) {
+ Entity stressSample = addEntity(schema, "Wena3StressSample");
+ addCommonTimeSampleProperties("AbstractStressSample", stressSample, user, device);
+ stressSample.addIntProperty("typeNum").notNull().codeBeforeGetterAndSetter(OVERRIDE);
+ stressSample.addIntProperty("stress").notNull().codeBeforeGetter(OVERRIDE);
+ return stressSample;
+ }
+
+ private static Entity addWena3ActivitySample(Schema schema, Entity user, Entity device) {
+ Entity activitySample = addEntity(schema, "Wena3ActivitySample");
+ addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
+ activitySample.addIntProperty(SAMPLE_STEPS).notNull();
+ activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetter(OVERRIDE);
+ addHeartRateProperties(activitySample);
+ return activitySample;
+ }
+
+ private static Entity addWena3HeartRateSample(Schema schema, Entity user, Entity device) {
+ Entity activitySample = addEntity(schema, "Wena3HeartRateSample");
+ addCommonTimeSampleProperties("AbstractHeartRateSample", activitySample, user, device);
+ activitySample.addIntProperty(SAMPLE_HEART_RATE).notNull();
+ return activitySample;
+ }
+
+ private static Entity addWena3EnergySample(Schema schema, Entity user, Entity device) {
+ Entity activitySample = addEntity(schema, "Wena3EnergySample");
+ addCommonTimeSampleProperties("AbstractTimeSample", activitySample, user, device);
+ activitySample.addIntProperty("energy").notNull();
+ return activitySample;
+ }
+
+ private static Entity addWena3CaloriesSample(Schema schema, Entity user, Entity device) {
+ Entity activitySample = addEntity(schema, "Wena3CaloriesSample");
+ addCommonTimeSampleProperties("AbstractTimeSample", activitySample, user, device);
+ activitySample.addIntProperty("calories").notNull();
+ return activitySample;
+ }
+
+ private static Entity addAppSpecificNotificationSettings(Schema schema, Entity device) {
+ Entity perAppSetting = addEntity(schema, "AppSpecificNotificationSetting");
+ perAppSetting.addStringProperty("packageId").notNull().primaryKey();
+ Property deviceId = perAppSetting.addLongProperty("deviceId").primaryKey().notNull().getProperty();
+ perAppSetting.addToOne(device, deviceId);
+ perAppSetting.addStringProperty("ledPattern");
+ perAppSetting.addStringProperty("vibrationPattern");
+ perAppSetting.addStringProperty("vibrationRepetition");
+ return perAppSetting;
+ }
}
diff --git a/README.md b/README.md
index 18483e7dd..f013e64a4 100644
--- a/README.md
+++ b/README.md
@@ -103,11 +103,13 @@ vendor's servers.
- PineTime (InfiniTime Firmware)
- Roidmi, Roidmi 3, Mojietu 3 (Bluetooth FM Transmitters)
- [SMA](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/SMA) Q2 (SMA-Q2-OSS Firmware)
-- [Sony Headphones](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Sony-Headphones)
- - LinkBuds S
- - WH-1000XM2, WH-1000XM3, WH-1000XM4, WH-1000XM5
- - WF-SP800N
- - WF-1000XM3, WF-1000XM4
+- Sony
+ - [Headphones](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Sony-Headphones)
+ - LinkBuds S
+ - WH-1000XM2, WH-1000XM3, WH-1000XM4, WH-1000XM5
+ - WF-SP800N
+ - WF-1000XM3, WF-1000XM4
+ - Wena 3
- Teclast H10, H30
- TLW64
- Vibratissimo (Experimental)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fbb06158c..173c1206b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -396,6 +396,22 @@
+
+
+
+
+
+
+ .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications;
+
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.appcompat.widget.SearchView;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.adapter.AppSpecificNotificationSettingsAppListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class AppSpecificNotificationSettingsActivity extends AbstractGBActivity {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AppSpecificNotificationSettingsActivity.class);
+
+ private AppSpecificNotificationSettingsAppListAdapter appListAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_app_specific_notification_settings);
+ RecyclerView appListView = findViewById(R.id.appListView);
+ appListView.setLayoutManager(new LinearLayoutManager(this));
+
+ GBDevice device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+ appListAdapter = new AppSpecificNotificationSettingsAppListAdapter(R.layout.item_app_specific_notification_app_list, this, device);
+
+ appListView.setAdapter(appListAdapter);
+
+ SearchView searchView = findViewById(R.id.appListViewSearch);
+ searchView.setIconifiedByDefault(false);
+ searchView.setIconified(false);
+ searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ appListAdapter.getFilter().filter(newText);
+ return true;
+ }
+ });
+ }
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ onBackPressed();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/app_specific_notifications/AppSpecificNotificationSettingsDetailActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/app_specific_notifications/AppSpecificNotificationSettingsDetailActivity.java
new file mode 100644
index 000000000..0f8b8d064
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/app_specific_notifications/AppSpecificNotificationSettingsDetailActivity.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications;
+
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.adapter.AppSpecificNotificationSettingsAppListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.database.AppSpecificNotificationSettingsRepository;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.AppSpecificNotificationSetting;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+
+public class AppSpecificNotificationSettingsDetailActivity extends AbstractGBActivity {
+ private AppSpecificNotificationSettingsRepository repository = null;
+ private String bundleId = null;
+
+ private GBDevice mDevice;
+ private DeviceCoordinator mCoordinator;
+
+ private List mLedPatternValues = new ArrayList<>();
+ private List mVibrationPatternValues = new ArrayList<>();
+ private List mVibrationCountValues = new ArrayList<>();
+
+ private Spinner mSpinnerLedPattern;
+ private Spinner mSpinnerVibrationPattern;
+ private Spinner mSpinnerVibrationCount;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_notification_per_app_setting_detail);
+ Button mButtonSave = findViewById(R.id.buttonSaveSettings);
+ Button mButtonDelete = findViewById(R.id.buttonDeleteSettings);
+ mSpinnerLedPattern = findViewById(R.id.spinnerLedType);
+ mSpinnerVibrationPattern = findViewById(R.id.spinnerVibraType);
+ mSpinnerVibrationCount = findViewById(R.id.spinnerVibraCount);
+
+ mDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+ mCoordinator = DeviceHelper.getInstance().getCoordinator(mDevice);
+
+ mLedPatternValues.clear();
+ for(AbstractNotificationPattern p: mCoordinator.getNotificationLedPatterns())
+ mLedPatternValues.add(p.getValue());
+
+ mVibrationPatternValues.clear();
+ for(AbstractNotificationPattern p: mCoordinator.getNotificationVibrationPatterns())
+ mVibrationPatternValues.add(p.getValue());
+
+ mVibrationCountValues.clear();
+ for(AbstractNotificationPattern p: mCoordinator.getNotificationVibrationRepetitionPatterns())
+ mVibrationCountValues.add(p.getValue());
+
+ if(!mCoordinator.supportsNotificationLedPatterns()) {
+ mSpinnerLedPattern.setEnabled(false);
+ } else {
+ mSpinnerLedPattern.setAdapter(
+ createAdapterFromArrayAddingDefault(mCoordinator.getNotificationLedPatterns())
+ );
+ }
+
+ if(!mCoordinator.supportsNotificationVibrationPatterns()) {
+ mSpinnerVibrationPattern.setEnabled(false);
+ } else {
+ mSpinnerVibrationPattern.setAdapter(
+ createAdapterFromArrayAddingDefault(mCoordinator.getNotificationVibrationPatterns())
+ );
+ }
+
+ if(!mCoordinator.supportsNotificationVibrationRepetitionPatterns()) {
+ mSpinnerVibrationCount.setEnabled(false);
+ } else {
+ mSpinnerVibrationCount.setAdapter(
+ createAdapterFromArrayAddingDefault(mCoordinator.getNotificationVibrationRepetitionPatterns())
+ );
+ }
+
+ String title = getIntent().getStringExtra(AppSpecificNotificationSettingsAppListAdapter.STRING_EXTRA_PACKAGE_TITLE);
+ setTitle(title);
+ bundleId = getIntent().getStringExtra(AppSpecificNotificationSettingsAppListAdapter.STRING_EXTRA_PACKAGE_NAME);
+
+ repository = new AppSpecificNotificationSettingsRepository(mDevice);
+ mButtonDelete.setOnClickListener(view -> {
+ repository.setSettingsForAppId(bundleId, null);
+ finish();
+ });
+
+ mButtonSave.setOnClickListener(view -> {
+ saveSettings();
+ finish();
+ });
+
+ AppSpecificNotificationSetting setting = repository.getSettingsForAppId(bundleId);
+ if(setting != null) {
+ if(setting.getLedPattern() != null) {
+ int idx = mLedPatternValues.indexOf(setting.getLedPattern());
+ if(idx >= 0) {
+ mSpinnerLedPattern.setSelection(idx + 1);
+ }
+ } else {
+ mSpinnerLedPattern.setSelection(0);
+ }
+
+ if(setting.getVibrationPattern() != null) {
+ int idx = mVibrationPatternValues.indexOf(setting.getVibrationPattern());
+ if(idx >= 0) {
+ mSpinnerVibrationPattern.setSelection(idx + 1);
+ }
+ } else {
+ mSpinnerVibrationPattern.setSelection(0);
+ }
+
+ if(setting.getVibrationRepetition() != null) {
+ int idx = mVibrationCountValues.indexOf(setting.getVibrationRepetition());
+ if(idx >= 0) {
+ mSpinnerVibrationCount.setSelection(idx + 1);
+ }
+ } else {
+ mSpinnerVibrationCount.setSelection(0);
+ }
+ }
+ }
+
+ private ArrayAdapter createAdapterFromArrayAddingDefault(AbstractNotificationPattern[] array) {
+ List allOptions = new ArrayList<>();
+ allOptions.add(getString(R.string.pref_default));
+ for(AbstractNotificationPattern s: array) allOptions.add(s.getUserReadableName(getApplicationContext()));
+
+ return new ArrayAdapter(this, android.R.layout.simple_spinner_item, allOptions);
+ }
+
+ private void saveSettings() {
+ String led = null;
+ String vibra = null;
+ String vibraTimes = null;
+
+ if(mSpinnerLedPattern.getSelectedItemPosition() > 0) {
+ led = mLedPatternValues.get(mSpinnerLedPattern.getSelectedItemPosition() - 1);
+ }
+
+ if(mSpinnerVibrationPattern.getSelectedItemPosition() > 0) {
+ vibra = mVibrationPatternValues.get(mSpinnerVibrationPattern.getSelectedItemPosition() - 1);
+ }
+
+ if(mSpinnerVibrationCount.getSelectedItemPosition() > 0) {
+ vibraTimes = mVibrationCountValues.get(mSpinnerVibrationCount.getSelectedItemPosition() - 1);
+ }
+
+ AppSpecificNotificationSetting setting = new AppSpecificNotificationSetting(
+ bundleId,
+ 0,
+ led,
+ vibra,
+ vibraTimes
+ );
+ repository.setSettingsForAppId(bundleId, setting);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
index 76ece3a50..1557f19e8 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
@@ -342,6 +342,7 @@ public class DeviceSettingsPreferenceConst {
public static final String PREFS_ACTIVITY_IN_DEVICE_CARD_SLEEP = "prefs_activity_in_device_card_sleep";
public static final String PREFS_ACTIVITY_IN_DEVICE_CARD_DISTANCE = "prefs_activity_in_device_card_distance";
public static final String PREFS_DEVICE_CHARTS_TABS = "charts_tabs";
+ public static final String PREFS_PER_APP_NOTIFICATION_SETTINGS = "pref_per_app_notification_settings";
public static final String PREF_UM25_SHOW_THRESHOLD_NOTIFICATION = "um25_current_threshold_notify";
public static final String PREF_UM25_SHOW_THRESHOLD = "um25_current_threshold";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
index 9ad779c26..cfe211cbc 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
@@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.CalBlacklistActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureContacts;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
+import nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications.AppSpecificNotificationSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.loyaltycards.LoyaltyCardsSettingsConst;
import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
@@ -49,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3SettingKeys;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
@@ -407,6 +409,8 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
addPreferenceHandlerFor(PREF_AMPM_ENABLED);
addPreferenceHandlerFor(PREF_SOUNDS);
addPreferenceHandlerFor(PREF_CAMERA_REMOTE);
+ addPreferenceHandlerFor(PREF_SCREEN_LIFT_WRIST);
+ addPreferenceHandlerFor(PREF_SYNC_CALENDAR);
addPreferenceHandlerFor(PREF_BLUETOOTH_CALLS_ENABLED);
addPreferenceHandlerFor(PREF_DISPLAY_CALLER);
@@ -900,6 +904,16 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
});
}
+ final Preference notificationSettings = findPreference(PREFS_PER_APP_NOTIFICATION_SETTINGS);
+ if(notificationSettings != null) {
+ notificationSettings.setOnPreferenceClickListener(preference -> {
+ final Intent intent = new Intent(getContext(), AppSpecificNotificationSettingsActivity.class);
+ intent.putExtra(GBDevice.EXTRA_DEVICE, getDevice());
+ startActivity(intent);
+ return true;
+ });
+ }
+
if (deviceSpecificSettingsCustomizer != null) {
deviceSpecificSettingsCustomizer.customizeSettings(this, prefs);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppSpecificNotificationSettingsAppListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppSpecificNotificationSettingsAppListAdapter.java
new file mode 100644
index 000000000..37490f0aa
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppSpecificNotificationSettingsAppListAdapter.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import androidx.recyclerview.widget.RecyclerView;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.app_specific_notifications.AppSpecificNotificationSettingsDetailActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+import static nodomain.freeyourgadget.gadgetbridge.GBApplication.packageNameToPebbleMsgSender;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AppSpecificNotificationSettingsAppListAdapter extends RecyclerView.Adapter implements Filterable {
+ protected static final Logger LOG = LoggerFactory.getLogger(AppSpecificNotificationSettingsAppListAdapter.class);
+
+ public static final String STRING_EXTRA_PACKAGE_NAME = "packageName";
+ public static final String STRING_EXTRA_PACKAGE_TITLE = "packageTitle";
+
+ private final List applicationInfoList;
+ private final int mLayoutId;
+ private final Context mContext;
+ private final PackageManager mPm;
+ private GBDevice mDevice;
+ private final IdentityHashMap mNameMap;
+
+ private ApplicationFilter applicationFilter;
+
+ public AppSpecificNotificationSettingsAppListAdapter(int layoutId, Context context, GBDevice device) {
+ mLayoutId = layoutId;
+ mContext = context;
+ mPm = context.getPackageManager();
+ mDevice = device;
+
+ applicationInfoList = getAllApplications();
+
+
+ // sort the package list by label and blacklist status
+ mNameMap = new IdentityHashMap<>(applicationInfoList.size());
+ for (ApplicationInfo ai : applicationInfoList) {
+ CharSequence name = mPm.getApplicationLabel(ai);
+ if (name == null) {
+ name = ai.packageName;
+ }
+ mNameMap.put(ai, name.toString());
+ }
+
+ Collections.sort(applicationInfoList, (ai1, ai2) -> {
+ final String s1 = mNameMap.get(ai1);
+ final String s2 = mNameMap.get(ai2);
+ return s1.compareToIgnoreCase(s2);
+ });
+
+ }
+
+ @Override
+ public AppNotificationSettingsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(mContext).inflate(mLayoutId, parent, false);
+ return new AppNotificationSettingsViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final AppNotificationSettingsViewHolder holder, int position) {
+ final ApplicationInfo appInfo = applicationInfoList.get(position);
+
+ holder.deviceAppVersionAuthorLabel.setText(appInfo.packageName);
+ holder.deviceAppNameLabel.setText(mNameMap.get(appInfo));
+ holder.deviceImageView.setImageDrawable(appInfo.loadIcon(mPm));
+
+ holder.itemView.setOnClickListener(v -> {
+ Intent intentStartNotificationFilterActivity = new Intent(mContext, AppSpecificNotificationSettingsDetailActivity.class);
+ intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_NAME, appInfo.packageName);
+ intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_TITLE, mNameMap.get(appInfo));
+ intentStartNotificationFilterActivity.putExtra(GBDevice.EXTRA_DEVICE, mDevice);
+ mContext.startActivity(intentStartNotificationFilterActivity);
+ });
+
+ holder.btnConfigureApp.setOnClickListener(view -> {
+ Intent intentStartNotificationFilterActivity = new Intent(mContext, AppSpecificNotificationSettingsDetailActivity.class);
+ intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_NAME, appInfo.packageName);
+ intentStartNotificationFilterActivity.putExtra(STRING_EXTRA_PACKAGE_TITLE, mNameMap.get(appInfo));
+ intentStartNotificationFilterActivity.putExtra(GBDevice.EXTRA_DEVICE, mDevice);
+ mContext.startActivity(intentStartNotificationFilterActivity);
+ });
+ }
+
+ /**
+ * Returns the applications for which the Gadgetbridge notifications are enabled.
+ */
+ public List getAllApplications() {
+ final Set allPackageNames = new HashSet<>();
+ final List ret = new LinkedList<>();
+ boolean filterInverted = !GBApplication.getPrefs().getString("notification_list_is_blacklist", "true").equals("true");
+
+ // Get apps for the current user
+ final List currentUserApps = mPm.getInstalledApplications(PackageManager.GET_META_DATA);
+ for (final ApplicationInfo app : currentUserApps) {
+ boolean blacklisted = GBApplication.appIsNotifBlacklisted(app.packageName) || GBApplication.appIsPebbleBlacklisted(packageNameToPebbleMsgSender(app.packageName));
+ if((!filterInverted && !blacklisted) || (filterInverted && blacklisted)) {
+ allPackageNames.add(app.packageName);
+ ret.add(app);
+ }
+ }
+
+ // Add all apps from other users (eg. manager profile)
+ try {
+ final UserHandle currentUser = Process.myUserHandle();
+ final LauncherApps launcher = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+ final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ final List userProfiles = um.getUserProfiles();
+ for (final UserHandle userProfile : userProfiles) {
+ if (userProfile.equals(currentUser)) {
+ continue;
+ }
+
+ final List userActivityList = launcher.getActivityList(null, userProfile);
+
+ for (final LauncherActivityInfo app : userActivityList) {
+ if (!allPackageNames.contains(app.getApplicationInfo().packageName)) {
+ boolean blacklisted = GBApplication.appIsNotifBlacklisted(app.getApplicationInfo().packageName) || GBApplication.appIsPebbleBlacklisted(packageNameToPebbleMsgSender(app.getApplicationInfo().packageName));
+ if((!filterInverted && !blacklisted) || (filterInverted && blacklisted)) {
+ allPackageNames.add(app.getApplicationInfo().packageName);
+ ret.add(app.getApplicationInfo());
+ }
+ }
+ }
+ }
+ } catch (final Exception e) {
+ LOG.error("Failed to get apps from other users", e);
+ }
+
+ return ret;
+ }
+
+
+ @Override
+ public int getItemCount() {
+ return applicationInfoList.size();
+ }
+
+ @Override
+ public Filter getFilter() {
+ if (applicationFilter == null)
+ applicationFilter = new ApplicationFilter(this, applicationInfoList);
+ return applicationFilter;
+ }
+
+ class AppNotificationSettingsViewHolder extends RecyclerView.ViewHolder {
+
+ final ImageView deviceImageView;
+ final TextView deviceAppVersionAuthorLabel;
+ final TextView deviceAppNameLabel;
+ final ImageView btnConfigureApp;
+
+ AppNotificationSettingsViewHolder(View itemView) {
+ super(itemView);
+
+ deviceImageView = itemView.findViewById(R.id.item_image);
+ deviceAppVersionAuthorLabel = itemView.findViewById(R.id.item_details);
+ deviceAppNameLabel = itemView.findViewById(R.id.item_name);
+ btnConfigureApp = itemView.findViewById(R.id.btn_configureApp);
+ }
+
+ }
+
+ private class ApplicationFilter extends Filter {
+
+ private final AppSpecificNotificationSettingsAppListAdapter adapter;
+ private final List originalList;
+ private final List filteredList;
+
+ private ApplicationFilter(AppSpecificNotificationSettingsAppListAdapter adapter, List originalList) {
+ super();
+ this.originalList = new ArrayList<>(originalList);
+ this.filteredList = new ArrayList<>();
+ this.adapter = adapter;
+ }
+
+ @Override
+ protected Filter.FilterResults performFiltering(CharSequence filter) {
+ filteredList.clear();
+ final Filter.FilterResults results = new Filter.FilterResults();
+
+ if (filter == null || filter.length() == 0)
+ filteredList.addAll(originalList);
+ else {
+ final String filterPattern = filter.toString().toLowerCase().trim();
+
+ for (ApplicationInfo ai : originalList) {
+ CharSequence name = mPm.getApplicationLabel(ai);
+ if (name.toString().toLowerCase().contains(filterPattern) ||
+ (ai.packageName.contains(filterPattern))) {
+ filteredList.add(ai);
+ }
+ }
+ }
+
+ results.values = filteredList;
+ results.count = filteredList.size();
+ return results;
+ }
+
+ @Override
+ protected void publishResults(CharSequence charSequence, Filter.FilterResults filterResults) {
+ adapter.applicationInfoList.clear();
+ adapter.applicationInfoList.addAll((List) filterResults.values);
+ adapter.notifyDataSetChanged();
+ }
+ }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/AppSpecificNotificationSettingsRepository.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/AppSpecificNotificationSettingsRepository.java
new file mode 100644
index 000000000..95957540a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/AppSpecificNotificationSettingsRepository.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.entities.AppSpecificNotificationSettingDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.AppSpecificNotificationSetting;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+// TODO: Distinguish by device ID as well to allow for different settings per device.
+// I didn't know how to get a device ID from a settings activity, so I left it as is.
+public class AppSpecificNotificationSettingsRepository {
+ private static final Logger LOG = LoggerFactory.getLogger(AppSpecificNotificationSettingsRepository.class);
+ private GBDevice mGbDevice;
+ public AppSpecificNotificationSettingsRepository(GBDevice gbDevice) {
+ mGbDevice = gbDevice;
+ }
+
+ @Nullable
+ public AppSpecificNotificationSetting getSettingsForAppId(String appId) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ DaoSession session = db.getDaoSession();
+ QueryBuilder qb = session.getAppSpecificNotificationSettingDao().queryBuilder();
+ return qb.where(
+ qb.and(AppSpecificNotificationSettingDao.Properties.PackageId.eq(appId),
+ AppSpecificNotificationSettingDao.Properties.DeviceId.eq(DBHelper.findDevice(mGbDevice, session).getId())
+ )).build().unique();
+ } catch (Exception e) {
+ LOG.error("Failed to get DB handle", e);
+ }
+ return null;
+ }
+
+ private void deleteForAppId(@NonNull String appId) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ DaoSession session = db.getDaoSession();
+ QueryBuilder qb = session.getAppSpecificNotificationSettingDao().queryBuilder();
+ qb.where(
+ qb.and(AppSpecificNotificationSettingDao.Properties.PackageId.eq(appId),
+ AppSpecificNotificationSettingDao.Properties.DeviceId.eq(DBHelper.findDevice(mGbDevice, session).getId())
+ )).buildDelete().executeDeleteWithoutDetachingEntities();
+ } catch (Exception e) {
+ LOG.error("Failed to get DB handle", e);
+ }
+ }
+
+ public void setSettingsForAppId(@NonNull String appId, @Nullable AppSpecificNotificationSetting settings) {
+ if (settings == null) {
+ deleteForAppId(appId);
+ } else {
+ settings.setPackageId(appId);
+ try (DBHandler db = GBApplication.acquireDB()) {
+ DaoSession session = db.getDaoSession();
+ Device dbDevice = DBHelper.findDevice(mGbDevice, session);
+ settings.setDeviceId(dbDevice.getId());
+ session.getAppSpecificNotificationSettingDao().insertOrReplace(settings);
+ } catch (Exception e) {
+ LOG.error("Failed to get DB handle", e);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
index 58954df3a..8f9bf9f44 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
@@ -57,6 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
@@ -472,4 +473,28 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
public EnumSet getInitialFlags() {
return EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING);
}
+
+ public boolean supportsNotificationVibrationPatterns() {
+ return false;
+ }
+
+ public boolean supportsNotificationVibrationRepetitionPatterns() {
+ return false;
+ }
+
+ public boolean supportsNotificationLedPatterns() {
+ return false;
+ }
+
+ public AbstractNotificationPattern[] getNotificationVibrationPatterns() {
+ return new AbstractNotificationPattern[0];
+ }
+
+ public AbstractNotificationPattern[] getNotificationVibrationRepetitionPatterns() {
+ return new AbstractNotificationPattern[0];
+ }
+
+ public AbstractNotificationPattern[] getNotificationLedPatterns() {
+ return new AbstractNotificationPattern[0];
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
index fd5623bfc..9c7c1c2fd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
@@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabi
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
@@ -563,4 +564,32 @@ public interface DeviceCoordinator {
Class extends DeviceSupport> getDeviceSupportClass();
EnumSet getInitialFlags();
+
+ /**
+ * Whether the device supports a variety of vibration patterns for notifications.
+ */
+ boolean supportsNotificationVibrationPatterns();
+ /**
+ * Whether the device supports a variety of vibration pattern repetitions for notifications.
+ */
+ boolean supportsNotificationVibrationRepetitionPatterns();
+
+ /**
+ * Whether the device supports a variety of LED patterns for notifications.
+ */
+ boolean supportsNotificationLedPatterns();
+ /**
+ * What vibration pattern repetitions for notifications are supported by the device.
+ */
+ AbstractNotificationPattern[] getNotificationVibrationPatterns();
+ /**
+ * What vibration pattern repetitions for notifications are supported by the device.
+ * Technote: this is not an int or a range because some devices (e.g. Wena 3) only allow
+ * a very specific set of value combinations here.
+ */
+ AbstractNotificationPattern[] getNotificationVibrationRepetitionPatterns();
+ /**
+ * What LED patterns for notifications are supported by the device.
+ */
+ AbstractNotificationPattern[] getNotificationLedPatterns();
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleCombiner.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleCombiner.java
new file mode 100644
index 000000000..13049c788
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleCombiner.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.BehaviorSample;
+
+// The sole purpose of this part of code is to take the Behavior samples
+// and drop them onto Steps samples (which are just your average activity data with the current
+// architecture of Gadgetbridge)
+public class SonyWena3ActivitySampleCombiner {
+ private static final Logger LOG = LoggerFactory.getLogger(SonyWena3ActivitySampleCombiner.class);
+ final SonyWena3ActivitySampleProvider activitySampleProvider;
+
+ public SonyWena3ActivitySampleCombiner(SonyWena3ActivitySampleProvider activitySampleProvider) {
+ this.activitySampleProvider = activitySampleProvider;
+ }
+
+ public void overlayBehaviorStartingAt(Date startDate, SonyWena3BehaviorSampleProvider behaviorSampleProvider) {
+ List behaviorSamples = behaviorSampleProvider.getAllSamples(startDate.getTime(), Long.MAX_VALUE);
+ List alteredSamples = new ArrayList<>();
+ for(Wena3BehaviorSample behaviorSample: behaviorSamples) {
+ List activitySamplesForThisRange = activitySampleProvider.getAllActivitySamples((int)(behaviorSample.getTimestampFrom() / 1000L), (int)(behaviorSample.getTimestampTo() / 1000L));
+
+ LOG.debug("Changing " + activitySamplesForThisRange.size() + " samples to behavior type: " + BehaviorSample.Type.LUT[behaviorSample.getRawKind()].name());
+ for(Wena3ActivitySample activitySample: activitySamplesForThisRange) {
+ activitySample.setRawKind(behaviorSample.getRawKind());
+ alteredSamples.add(activitySample);
+ }
+ }
+ activitySampleProvider.addGBActivitySamples(alteredSamples.toArray(new Wena3ActivitySample[alteredSamples.size()]));
+ }
+
+ public void overlayHeartRateStartingAt(Date startDate, SonyWena3HeartRateSampleProvider heartRateSampleProvider) {
+ List heartRateSamples = heartRateSampleProvider.getAllSamples(startDate.getTime(), Long.MAX_VALUE);
+ List alteredSamples = new ArrayList<>();
+ for(int i = 0; i < heartRateSamples.size(); i++) {
+ HeartRateSample currentSample = heartRateSamples.get(i);
+ HeartRateSample nextSample = (i == heartRateSamples.size() - 1) ? null : heartRateSamples.get(i + 1);
+
+ List activitySamplesInRange = activitySampleProvider.getAllActivitySamples(
+ (int) (currentSample.getTimestamp() / 1000L),
+ (nextSample == null ? Integer.MAX_VALUE : ((int) (nextSample.getTimestamp() / 1000L)))
+ );
+
+ LOG.debug("Changing " + activitySamplesInRange.size() + " samples to heart rate: " + currentSample.getHeartRate());
+ for(Wena3ActivitySample activitySample: activitySamplesInRange) {
+ activitySample.setHeartRate(currentSample.getHeartRate());
+ alteredSamples.add(activitySample);
+ }
+ }
+ activitySampleProvider.addGBActivitySamples(alteredSamples.toArray(new Wena3ActivitySample[alteredSamples.size()]));
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleProvider.java
new file mode 100644
index 000000000..4c2034e9b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3ActivitySampleProvider.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import de.greenrobot.dao.AbstractDao;
+import de.greenrobot.dao.Property;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.BehaviorSample;
+
+public class SonyWena3ActivitySampleProvider extends AbstractSampleProvider {
+ public SonyWena3ActivitySampleProvider(GBDevice device, DaoSession session) {
+ super(device, session);
+ }
+
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getWena3ActivitySampleDao();
+ }
+
+ @Nullable
+ @Override
+ protected Property getRawKindSampleProperty() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return Wena3ActivitySampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return Wena3ActivitySampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ public int normalizeType(int rawType) {
+ if(rawType < 0 || rawType >= BehaviorSample.Type.LUT.length) return ActivityKind.TYPE_UNKNOWN;
+
+ BehaviorSample.Type internalType = BehaviorSample.Type.LUT[rawType];
+ switch(internalType) {
+ case NOT_WEARING:
+ return ActivityKind.TYPE_NOT_WORN;
+
+ case WALK:
+ return ActivityKind.TYPE_WALKING;
+ case RUN:
+ return ActivityKind.TYPE_RUNNING;
+ case EXERCISE:
+ return ActivityKind.TYPE_EXERCISE;
+
+ case SLEEP_LIGHT:
+ return ActivityKind.TYPE_LIGHT_SLEEP;
+ case SLEEP_REM:
+ return ActivityKind.TYPE_REM_SLEEP;
+ case SLEEP_DEEP:
+ return ActivityKind.TYPE_DEEP_SLEEP;
+
+ case STATIC:
+ case SLEEP_AWAKE:
+ case UNKNOWN:
+ default:
+ return ActivityKind.TYPE_UNKNOWN;
+ }
+ }
+
+ @Override
+ public int toRawActivityKind(int activityKind) {
+ switch(activityKind) {
+ case ActivityKind.TYPE_NOT_MEASURED:
+ case ActivityKind.TYPE_NOT_WORN:
+ return BehaviorSample.Type.NOT_WEARING.ordinal();
+ case ActivityKind.TYPE_WALKING:
+ return BehaviorSample.Type.WALK.ordinal();
+ case ActivityKind.TYPE_RUNNING:
+ return BehaviorSample.Type.RUN.ordinal();
+ case ActivityKind.TYPE_LIGHT_SLEEP:
+ return BehaviorSample.Type.SLEEP_LIGHT.ordinal();
+ case ActivityKind.TYPE_REM_SLEEP:
+ return BehaviorSample.Type.SLEEP_REM.ordinal();
+ case ActivityKind.TYPE_DEEP_SLEEP:
+ return BehaviorSample.Type.SLEEP_DEEP.ordinal();
+ case ActivityKind.TYPE_EXERCISE:
+ return BehaviorSample.Type.EXERCISE.ordinal();
+ default:
+ return BehaviorSample.Type.UNKNOWN.ordinal();
+ }
+ }
+
+ @Override
+ public float normalizeIntensity(int rawIntensity) {
+ return 0;
+ }
+
+ @Override
+ public Wena3ActivitySample createActivitySample() {
+ return new Wena3ActivitySample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3BehaviorSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3BehaviorSampleProvider.java
new file mode 100644
index 000000000..1cccf0db6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3BehaviorSampleProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+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.Wena3BehaviorSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SonyWena3BehaviorSampleProvider extends AbstractTimeSampleProvider {
+ public SonyWena3BehaviorSampleProvider(final GBDevice device, final DaoSession session) {
+ super(device, session);
+ }
+
+ @NonNull
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getWena3BehaviorSampleDao();
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return Wena3BehaviorSampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return Wena3BehaviorSampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ public Wena3BehaviorSample createSample() {
+ return new Wena3BehaviorSample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3CaloriesSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3CaloriesSampleProvider.java
new file mode 100644
index 000000000..602dde1fb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3CaloriesSampleProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+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.Wena3CaloriesSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3CaloriesSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SonyWena3CaloriesSampleProvider extends AbstractTimeSampleProvider {
+ public SonyWena3CaloriesSampleProvider(final GBDevice device, final DaoSession session) {
+ super(device, session);
+ }
+
+ @NonNull
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getWena3CaloriesSampleDao();
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return Wena3CaloriesSampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return Wena3CaloriesSampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ public Wena3CaloriesSample createSample() {
+ return new Wena3CaloriesSample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Constants.java
new file mode 100644
index 000000000..8a75a5e46
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Constants.java
@@ -0,0 +1,64 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+import android.graphics.Color;
+
+import java.util.UUID;
+
+public class SonyWena3Constants {
+ public static final String BT_DEVICE_NAME = "WNW-21A";
+ private static final String uuidTemplate = "4EFD%s-A6C1-16F0-062F-F196CF496695";
+
+ public static final UUID COMMON_SERVICE_UUID = UUID.fromString(String.format(uuidTemplate, "1501"));
+ public static final UUID COMMON_SERVICE_CHARACTERISTIC_MODE_UUID = UUID.fromString(String.format(uuidTemplate, "1503"));
+ public static final UUID COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID = UUID.fromString(String.format(uuidTemplate, "1514"));
+ public static final UUID COMMON_SERVICE_CHARACTERISTIC_INFO_UUID = UUID.fromString(String.format(uuidTemplate, "1520"));
+ public static final UUID COMMON_SERVICE_CHARACTERISTIC_STATE_UUID = UUID.fromString(String.format(uuidTemplate, "1521"));
+
+ public static final UUID NOTIFICATION_SERVICE_UUID = UUID.fromString(String.format(uuidTemplate, "4001"));
+ public static final UUID NOTIFICATION_SERVICE_CHARACTERISTIC_UUID = UUID.fromString(String.format(uuidTemplate, "4002"));
+
+ public static final UUID ACTIVITY_LOG_SERVICE_UUID = UUID.fromString(String.format(uuidTemplate, "3001"));
+ public static final UUID ACTIVITY_LOG_CHARACTERISTIC_UUID = UUID.fromString(String.format(uuidTemplate, "3002"));
+
+
+ public static int[] LED_PRESETS = {
+ Color.rgb(255, 0, 0),
+ Color.rgb(255, 255, 0),
+ Color.rgb(0, 255, 0),
+ Color.rgb(0, 255, 255),
+ Color.rgb(0, 0, 255),
+ Color.rgb(255, 0, 255),
+ Color.rgb(255, 255, 255)
+ };
+
+ public static final long EPOCH_START = 1577836800000L;
+
+ public static final int ALARM_SLOTS = 9;
+ public static final int ALARM_DEFAULT_SMART_WAKEUP_MARGIN_MINUTES = 10;
+
+ public static final String DB_FIELD_VO2_DATAPOINT_NUMBER = "datapoint";
+ public static final String DB_FIELD_VO2_VALUE = "vo2";
+ public static final String DB_FIELD_STRESS = "stress";
+ public static final String DB_FIELD_STEPS = "steps";
+ public static final String DB_FIELD_HEART_RATE = "heartRate";
+ public static final String DB_FIELD_ENERGY = "energy";
+ public static final String DB_FIELD_CALORIES = "calories";
+
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Coordinator.java
new file mode 100644
index 000000000..3bff6488a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Coordinator.java
@@ -0,0 +1,302 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+import android.app.Activity;
+import android.bluetooth.le.ScanFilter;
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3CaloriesSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3EnergySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3StressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3Vo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
+import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.SonyWena3DeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.LedColor;
+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;
+
+public class SonyWena3Coordinator extends AbstractBLEDeviceCoordinator {
+ @Override
+ public DeviceType getDeviceType() {
+ return DeviceType.SONY_WENA_3;
+ }
+
+ @Nullable
+ @Override
+ public Class extends Activity> getPairingActivity() {
+ return null;
+ }
+
+ @Override
+ public String getManufacturer() {
+ return "Sony";
+ }
+
+ @Override
+ public boolean supportsAppsManagement(GBDevice device) {
+ return false;
+ }
+
+ @Override
+ public Class extends Activity> getAppsManagementActivity() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Class extends DeviceSupport> getDeviceSupportClass() {
+ return SonyWena3DeviceSupport.class;
+ }
+
+ @Override
+ public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(GBDevice device) {
+ return new SonyWena3SettingsCustomizer();
+ }
+
+ @NonNull
+ @Override
+ public Collection extends ScanFilter> createBLEScanFilters() {
+ ScanFilter.Builder builder = new ScanFilter.Builder();
+ builder.setDeviceName(SonyWena3Constants.BT_DEVICE_NAME);
+ ArrayList result = new ArrayList<>();
+ result.add(builder.build());
+ return result;
+ }
+
+ @Override
+ protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) {
+ Long deviceId = device.getId();
+ QueryBuilder> qb;
+
+ qb = session.getWena3HeartRateSampleDao().queryBuilder();
+ qb.where(Wena3HeartRateSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+ qb = session.getWena3BehaviorSampleDao().queryBuilder();
+ qb.where(Wena3BehaviorSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+ qb = session.getWena3CaloriesSampleDao().queryBuilder();
+ qb.where(Wena3CaloriesSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+ qb = session.getWena3EnergySampleDao().queryBuilder();
+ qb.where(Wena3EnergySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+ qb = session.getWena3ActivitySampleDao().queryBuilder();
+ qb.where(Wena3ActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+ qb = session.getWena3Vo2SampleDao().queryBuilder();
+ qb.where(Wena3Vo2SampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+ qb = session.getWena3StressSampleDao().queryBuilder();
+ qb.where(Wena3StressSampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
+ }
+
+ @NonNull
+ @Override
+ public DeviceType getSupportedType(GBDeviceCandidate candidate) {
+ if(SonyWena3Constants.BT_DEVICE_NAME.equals(candidate.getName())) {
+ return DeviceType.SONY_WENA_3;
+ }
+ return DeviceType.UNKNOWN;
+ }
+
+ @Override
+ public int getBondingStyle() {
+ return BONDING_STYLE_BOND;
+ }
+
+ @Override
+ public boolean supportsCalendarEvents() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsRealtimeData() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsActivityDataFetching() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsActivityTracking() {
+ return true;
+ }
+
+
+ @Override
+ public boolean supportsAppReordering() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsStressMeasurement() {
+ return true;
+ }
+
+ @Override
+ public TimeSampleProvider extends StressSample> getStressSampleProvider(GBDevice device, DaoSession session) {
+ return new SonyWena3StressSampleProvider(device, session);
+ }
+
+ @Override
+ public boolean supportsSpo2() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsHeartRateMeasurement(GBDevice device) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsHeartRateStats() {
+ return true;
+ }
+
+ @Override
+ public SampleProvider extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
+ return new SonyWena3ActivitySampleProvider(device, session);
+ }
+
+ @Override
+ public TimeSampleProvider extends HeartRateSample> getHeartRateRestingSampleProvider(GBDevice device, DaoSession session) {
+ return new SonyWena3HeartRateSampleProvider(device, session);
+ }
+
+
+ @Override
+ public InstallHandler findInstallHandler(Uri uri, Context context) {
+ return null;
+ }
+
+ @Override
+ public boolean supportsScreenshots() {
+ return false;
+ }
+
+ @Override
+ public int getAlarmSlotCount(GBDevice device) {
+ return SonyWena3Constants.ALARM_SLOTS;
+ }
+
+ @Override
+ public boolean supportsSmartWakeup(GBDevice device) {
+ return true;
+ }
+
+ @Override
+ public boolean supportsMusicInfo() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsFindDevice() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsWeather() {
+ return true;
+ }
+
+ @Override
+ public boolean isExperimental() {
+ return false;
+ }
+
+ @Override
+ public String[] getSupportedLanguageSettings(GBDevice device) {
+ return new String[]{
+ "auto",
+ "en_US",
+ "ja_JP"
+ };
+ }
+
+ @Override
+ public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
+ return new int[]{
+ R.xml.devicesettings_notifications_enable,
+ R.xml.devicesettings_sync_calendar,
+ R.xml.devicesettings_wearlocation,
+ R.xml.devicesettings_donotdisturb_no_auto,
+ R.xml.devicesettings_wena3_auto_power_off,
+ R.xml.devicesettings_goal_notification,
+ R.xml.devicesettings_wena3,
+ };
+ }
+
+
+ public boolean supportsNotificationVibrationPatterns() {
+ return true;
+ }
+
+ public boolean supportsNotificationVibrationRepetitionPatterns() {
+ return true;
+ }
+
+ public boolean supportsNotificationLedPatterns() {
+ return true;
+ }
+
+ public AbstractNotificationPattern[] getNotificationVibrationPatterns() {
+ return new AbstractNotificationPattern[] {
+ VibrationKind.NONE, VibrationKind.BASIC,
+ VibrationKind.CONTINUOUS, VibrationKind.RAPID,
+ VibrationKind.TRIPLE, VibrationKind.STEP_UP, VibrationKind.STEP_DOWN,
+ VibrationKind.WARNING, VibrationKind.SIREN, VibrationKind.SHORT
+ };
+ }
+
+ public AbstractNotificationPattern[] getNotificationVibrationRepetitionPatterns() {
+ return new AbstractNotificationPattern[] {
+ VibrationCount.ONCE, VibrationCount.TWICE, VibrationCount.THREE, VibrationCount.FOUR,
+ VibrationCount.INDEFINITE
+ };
+ }
+
+ public AbstractNotificationPattern[] getNotificationLedPatterns() {
+ return new AbstractNotificationPattern[] {
+ LedColor.NONE, LedColor.RED, LedColor.YELLOW, LedColor.GREEN,
+ LedColor.CYAN, LedColor.BLUE, LedColor.PURPLE, LedColor.WHITE
+ };
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3EnergySampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3EnergySampleProvider.java
new file mode 100644
index 000000000..ba633c841
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3EnergySampleProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+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.Wena3EnergySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3EnergySampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SonyWena3EnergySampleProvider extends AbstractTimeSampleProvider {
+ public SonyWena3EnergySampleProvider(final GBDevice device, final DaoSession session) {
+ super(device, session);
+ }
+
+ @NonNull
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getWena3EnergySampleDao();
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return Wena3EnergySampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return Wena3EnergySampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ public Wena3EnergySample createSample() {
+ return new Wena3EnergySample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3HeartRateSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3HeartRateSampleProvider.java
new file mode 100644
index 000000000..490dd3b06
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3HeartRateSampleProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+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.Wena3HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SonyWena3HeartRateSampleProvider extends AbstractTimeSampleProvider {
+ public SonyWena3HeartRateSampleProvider(final GBDevice device, final DaoSession session) {
+ super(device, session);
+ }
+
+ @NonNull
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getWena3HeartRateSampleDao();
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return Wena3HeartRateSampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return Wena3HeartRateSampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ public Wena3HeartRateSample createSample() {
+ return new Wena3HeartRateSample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingKeys.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingKeys.java
new file mode 100644
index 000000000..c7cba86f9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingKeys.java
@@ -0,0 +1,52 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+public class SonyWena3SettingKeys {
+ // ------ Booleans
+ public static final String RICH_DESIGN_MODE = "pref_wena3_rich_design_mode";
+ public static final String LARGE_FONT_SIZE = "pref_wena3_large_font_size";
+ public static final String WEATHER_IN_STATUSBAR = "pref_wena3_weather_in_statusbar";
+ public static final String SMART_VIBRATION = "pref_wena3_vibration_smart";
+ public static final String RECEIVE_CALLS = "pref_wena3_receive_calls";
+ public static final String BACKGROUND_SYNC = "pref_wena3_background_sync";
+
+ public static final String AUTO_POWER_SCHEDULE_KIND = "pref_wena3_power_schedule_kind";
+ public static final String AUTO_POWER_SCHEDULE_START_HHMM = "pref_wena3_power_schedule_start";
+ public static final String AUTO_POWER_SCHEDULE_END_HHMM = "pref_wena3_power_schedule_end";
+
+ public static final String LEFT_HOME_ICON = "pref_wena3_home_icon_left";
+ public static final String CENTER_HOME_ICON = "pref_wena3_home_icon_center";
+ public static final String RIGHT_HOME_ICON = "pref_wena3_home_icon_right";
+ public static final String MENU_ICON_CSV_KEY = "pref_wena3_menu_icon_csv";
+ public static final String STATUS_PAGE_CSV_KEY = "pref_wena3_status_pages_csv";
+ public static final int MAX_STATUS_PAGES = 7;
+
+ public static final String DEFAULT_CALL_LED_COLOR = "pref_wena3_default_call_led_color";
+ public static final String DEFAULT_LED_COLOR = "pref_wena3_default_led_color";
+ public static final String DEFAULT_CALL_VIBRATION_PATTERN = "pref_wena3_call_default_vibration";
+ public static final String DEFAULT_VIBRATION_PATTERN = "pref_wena3_default_vibration";
+ public static final String BUTTON_LONG_PRESS_ACTION = "pref_wena3_button_long_action";
+ public static final String BUTTON_DOUBLE_PRESS_ACTION = "pref_wena3_button_double_action";
+ public static final String VIBRATION_STRENGTH = "pref_wena3_vibration_strength";
+ // ------ Ints
+ public static final String SMART_WAKEUP_MARGIN_MINUTES = "pref_wena3_smart_wakeup_margin";
+ public static final String DAY_START_HOUR = "pref_wena3_day_start_hour";
+ public static final String DEFAULT_VIBRATION_REPETITION = "pref_wena3_default_vibration_repetition";
+ public static final String DEFAULT_CALL_VIBRATION_REPETITION = "pref_wena3_default_call_vibration_repetition";
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingsCustomizer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingsCustomizer.java
new file mode 100644
index 000000000..eaedab22c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3SettingsCustomizer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+import android.os.Parcel;
+
+import androidx.preference.Preference;
+
+import java.util.Collections;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class SonyWena3SettingsCustomizer implements DeviceSpecificSettingsCustomizer {
+ @Override
+ public void customizeSettings(DeviceSpecificSettingsHandler handler, Prefs prefs) {
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.RICH_DESIGN_MODE);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.LARGE_FONT_SIZE);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.WEATHER_IN_STATUSBAR);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.SMART_VIBRATION);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.SMART_WAKEUP_MARGIN_MINUTES);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.VIBRATION_STRENGTH);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.LEFT_HOME_ICON);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.CENTER_HOME_ICON);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.RIGHT_HOME_ICON);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.DAY_START_HOUR);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_KIND);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_START_HHMM);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_END_HHMM);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.BUTTON_DOUBLE_PRESS_ACTION);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.BUTTON_LONG_PRESS_ACTION);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.MENU_ICON_CSV_KEY);
+ handler.addPreferenceHandlerFor(SonyWena3SettingKeys.STATUS_PAGE_CSV_KEY);
+ }
+
+ @Override
+ public void onPreferenceChange(Preference preference, DeviceSpecificSettingsHandler handler) {
+ }
+
+ @Override
+ public Set getPreferenceKeysWithSummary() {
+ return Collections.emptySet();
+ }
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public SonyWena3SettingsCustomizer createFromParcel(final Parcel in) {
+ return new SonyWena3SettingsCustomizer();
+ }
+
+ @Override
+ public SonyWena3SettingsCustomizer[] newArray(final int size) {
+ return new SonyWena3SettingsCustomizer[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel dest, final int flags) {
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3StressSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3StressSampleProvider.java
new file mode 100644
index 000000000..034919949
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3StressSampleProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+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.Wena3StressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3StressSampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SonyWena3StressSampleProvider extends AbstractTimeSampleProvider {
+ public SonyWena3StressSampleProvider(final GBDevice device, final DaoSession session) {
+ super(device, session);
+ }
+
+ @NonNull
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getWena3StressSampleDao();
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return Wena3StressSampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return Wena3StressSampleDao.Properties.DeviceId;
+ }
+
+ @NonNull
+ @Override
+ public List getAllSamples(long timestampFrom, long timestampTo) {
+ List samples = super.getAllSamples(timestampFrom, timestampTo);
+ for(Wena3StressSample sample: samples) {
+ if(sample.getStress() < -100 || sample.getStress() > 100) {
+ sample.setStress(-1);
+ } else {
+ // Move from the original -100 .. 100 range to 0 .. 100
+ sample.setStress(Math.round(((float)sample.getStress() + 100.0f) / 2.0f));
+ }
+ }
+ return samples;
+ }
+
+ @Override
+ public Wena3StressSample createSample() {
+ return new Wena3StressSample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Vo2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Vo2SampleProvider.java
new file mode 100644
index 000000000..b5c62411d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sony/wena3/SonyWena3Vo2SampleProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3;
+
+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.Wena3Vo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3Vo2SampleDao;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class SonyWena3Vo2SampleProvider extends AbstractTimeSampleProvider {
+ public SonyWena3Vo2SampleProvider(final GBDevice device, final DaoSession session) {
+ super(device, session);
+ }
+
+ @NonNull
+ @Override
+ public AbstractDao getSampleDao() {
+ return getSession().getWena3Vo2SampleDao();
+ }
+
+ @NonNull
+ @Override
+ protected Property getTimestampSampleProperty() {
+ return Wena3Vo2SampleDao.Properties.Timestamp;
+ }
+
+ @NonNull
+ @Override
+ protected Property getDeviceIdentifierSampleProperty() {
+ return Wena3Vo2SampleDao.Properties.DeviceId;
+ }
+
+ @Override
+ public Wena3Vo2Sample createSample() {
+ return new Wena3Vo2Sample();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AbstractNotificationPattern.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AbstractNotificationPattern.java
new file mode 100644
index 000000000..f9bd647fd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/AbstractNotificationPattern.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import android.content.Context;
+
+public interface AbstractNotificationPattern {
+ String getUserReadableName(Context context);
+ String getValue();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
index ad5691d18..2315cd175 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java
@@ -148,6 +148,7 @@ public enum DeviceType {
ASTEROIDOS(540, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_asteroidos),
SOFLOW_SO6(550, R.drawable.ic_device_vesc, R.drawable.ic_device_vesc_disabled, R.string.devicetype_soflow_s06),
WITHINGS_STEEL_HR(560, R.drawable.ic_device_watchxplus, R.drawable.ic_device_watchxplus_disabled, R.string.withings_steel_hr),
+ SONY_WENA_3(570, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_sony_wena3),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
private final int key;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java
new file mode 100644
index 000000000..9d4c27d15
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/SonyWena3DeviceSupport.java
@@ -0,0 +1,1067 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3BehaviorSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3CaloriesSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Constants;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3EnergySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3HeartRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3SettingKeys;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3StressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Vo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.database.AppSpecificNotificationSettingsRepository;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3CaloriesSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3EnergySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.entities.AppSpecificNotificationSetting;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3StressSample;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3Vo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.ActivitySyncPacketProcessor;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers.BehaviorPacketParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers.CaloriesPacketParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers.EnergyPacketParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers.HeartRatePacketParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers.StepsPacketParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers.StressPacketParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers.Vo2MaxPacketParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncDataPacket;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncStartPacket;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncTimePacketTypeA;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncTimePacketTypeB;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.calendar.CalendarEntry;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.NotificationArrival;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.NotificationRemoval;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.LedColor;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.NotificationFlags;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.NotificationKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.VibrationKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.VibrationOptions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.AlarmListSettings;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.AutoPowerOffSettings;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.BodyPropertiesSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.CalendarNotificationEnableSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.CameraAppTypeSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.DayStartHourSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.DeviceButtonActionSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.DisplaySetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.DoNotDisturbSettings;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.GoalStepsSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.HomeIconOrderSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.MenuIconSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.SingleAlarmSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.StatusPageOrderSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.TimeSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.TimeZoneSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.VibrationSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.DeviceButtonActionId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.DisplayDesign;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.DisplayOrientation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.FontSize;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.GenderSetting;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.HomeIconId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.Language;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.MenuIconId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.StatusPageId;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.VibrationStrength;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status.BatteryLevelInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status.DeviceInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status.MusicInfo;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status.NotificationServiceStatusRequest;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status.StatusRequestType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status.Weather;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status.WeatherDay;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status.WeatherReport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+public class SonyWena3DeviceSupport extends AbstractBTLEDeviceSupport {
+ private static final int INCOMING_CALL_ID = 3939;
+ private static final Logger LOG = LoggerFactory.getLogger(SonyWena3DeviceSupport.class);
+ private String lastMusicInfo = null;
+ private final List calendarEvents = new ArrayList<>();
+ private final ActivitySyncPacketProcessor activitySyncHandler = new ActivitySyncPacketProcessor();
+ private AppSpecificNotificationSettingsRepository perAppNotificationSettingsRepository = null;
+
+ public SonyWena3DeviceSupport() {
+ super(LoggerFactory.getLogger(SonyWena3DeviceSupport.class));
+ addSupportedService(SonyWena3Constants.COMMON_SERVICE_UUID);
+ addSupportedService(SonyWena3Constants.NOTIFICATION_SERVICE_UUID);
+ addSupportedService(SonyWena3Constants.ACTIVITY_LOG_SERVICE_UUID);
+
+ activitySyncHandler.registerParser(new StepsPacketParser());
+ activitySyncHandler.registerParser(new HeartRatePacketParser());
+ activitySyncHandler.registerParser(new StressPacketParser());
+ activitySyncHandler.registerParser(new EnergyPacketParser());
+ activitySyncHandler.registerParser(new BehaviorPacketParser());
+ activitySyncHandler.registerParser(new CaloriesPacketParser());
+ activitySyncHandler.registerParser(new Vo2MaxPacketParser());
+ }
+ @Override
+ public boolean useAutoConnect() {
+ return true;
+ }
+
+ @Override
+ protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
+ builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
+ if(perAppNotificationSettingsRepository == null) {
+ perAppNotificationSettingsRepository = new AppSpecificNotificationSettingsRepository(getDevice());
+ }
+ getDevice().setFirmwareVersion("...");
+ getDevice().setFirmwareVersion2("...");
+
+ sendAllSettings(builder);
+ sendAllCalendarEvents(builder);
+
+ // Get battery state
+ builder.read(getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_STATE_UUID));
+
+ // Subscribe to updates
+ builder.notify(getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_STATE_UUID), true);
+ builder.notify(getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID), true);
+ builder.notify(getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_INFO_UUID), true);
+ builder.notify(getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_MODE_UUID), true);
+ builder.notify(getCharacteristic(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID), true);
+ builder.notify(getCharacteristic(SonyWena3Constants.ACTIVITY_LOG_CHARACTERISTIC_UUID), true);
+
+ // Get serial number and firmware version
+ builder.read(getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_INFO_UUID));
+
+ // Finally, sync activity data
+ requestActivityDataDownload(builder, false);
+
+ builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
+ CalendarReceiver.forceSync();
+ return builder;
+ }
+
+ @Override
+ public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ if(characteristic.getUuid().equals(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_STATE_UUID)) {
+ BatteryLevelInfo stateInfo = new BatteryLevelInfo(characteristic.getValue());
+ handleGBDeviceEvent(stateInfo.toDeviceEvent());
+ return true;
+ }
+ else if (characteristic.getUuid().equals(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID)) {
+ NotificationServiceStatusRequest request = new NotificationServiceStatusRequest(characteristic.getValue());
+ if(request.requestType == StatusRequestType.MUSIC_INFO_FETCH.value) {
+ LOG.debug("Request for music info received");
+ sendMusicInfo(lastMusicInfo);
+ return true;
+ }
+ else if(request.requestType == StatusRequestType.LOCATE_PHONE.value) {
+ LOG.debug("Request for find phone received");
+ GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
+ findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
+ evaluateGBDeviceEvent(findPhoneEvent);
+ return true;
+ }
+ else if(request.requestType == StatusRequestType.BACKGROUND_SYNC_REQUEST.value) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ boolean enableSync = prefs.getBoolean(SonyWena3SettingKeys.BACKGROUND_SYNC, true);
+ if(enableSync) {
+ LOG.info("Request for background activity sync received");
+ requestActivityDataDownload(null, false);
+ }
+ return true;
+ }
+ else if(request.requestType == StatusRequestType.GET_CALENDAR.value) {
+ CalendarReceiver.forceSync();
+ sendAllCalendarEvents(null);
+ }
+ else {
+ LOG.warn("Unknown NotificationServiceStatusRequest " + request.requestType);
+ }
+ } else if(characteristic.getUuid().equals(SonyWena3Constants.ACTIVITY_LOG_CHARACTERISTIC_UUID)) {
+ ActivitySyncDataPacket asdp = new ActivitySyncDataPacket(characteristic.getValue());
+ activitySyncHandler.receivePacket(asdp, getDevice());
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ if(characteristic.getUuid().equals(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_STATE_UUID)) {
+ BatteryLevelInfo stateInfo = new BatteryLevelInfo(characteristic.getValue());
+ handleGBDeviceEvent(stateInfo.toDeviceEvent());
+ return true;
+ } else if(characteristic.getUuid().equals(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_INFO_UUID)) {
+ DeviceInfo deviceInfo = new DeviceInfo(characteristic.getValue());
+ handleGBDeviceEvent(deviceInfo.toDeviceEvent());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+ return super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
+ }
+
+
+ private void sendCurrentTime(@Nullable TransactionBuilder b) {
+ try {
+ TransactionBuilder builder = b == null ? performInitialized("updateDateTime") : b;
+
+ TimeZone tz = TimeZone.getDefault();
+ Date currentTime = new Date();
+
+ builder.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ new TimeSetting(currentTime).toByteArray()
+ );
+
+ builder.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ new TimeZoneSetting(tz, currentTime).toByteArray()
+ );
+
+ if(b == null) performImmediately(builder);
+ } catch (IOException e) {
+ LOG.warn("Unable to send current time", e);
+ }
+ }
+
+ private void sendMusicInfo(@Nullable String musicInfo) {
+ try {
+ TransactionBuilder builder = performInitialized("updateMusic");
+
+ builder.write(
+ getCharacteristic(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID),
+ new MusicInfo(musicInfo != null ? musicInfo: "").toByteArray()
+ );
+
+ performImmediately(builder);
+ } catch (IOException e) {
+ LOG.warn("Unable to send music info", e);
+ }
+ }
+
+ @Override
+ public void onFetchRecordedData(int dataTypes) {
+ requestActivityDataDownload(null, false);
+ }
+
+ private void requestActivityDataDownload(@Nullable TransactionBuilder b, boolean syncAll) {
+ try {
+ TransactionBuilder builder = b == null ? performInitialized("activitySync") : b;
+ BluetoothGattCharacteristic sportsCharacteristic = getCharacteristic(SonyWena3Constants.ACTIVITY_LOG_CHARACTERISTIC_UUID);
+
+ Date stepLastSyncTime = null;
+ Date heartLastSyncTime = null;
+ Date behaviorLastSyncTime = null;
+ Date vo2LastSyncTime = null;
+ Date stressLastSyncTime = null;
+ Date energyLastSyncTime = null;
+ Date caloriesLastSyncTime = null;
+ Date eventsLastSyncTime = null; // TODO: find out what this is
+
+ if(!syncAll) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ Wena3HeartRateSample heartSample = new SonyWena3HeartRateSampleProvider(getDevice(), db.getDaoSession()).getLatestSample();
+ if(heartSample != null) {
+ heartLastSyncTime = new Date(heartSample.getTimestamp());
+ }
+
+ Wena3StressSample stressSample = new SonyWena3StressSampleProvider(getDevice(), db.getDaoSession()).getLatestSample();
+ if(stressSample != null) {
+ stressLastSyncTime = new Date(stressSample.getTimestamp());
+ }
+
+ Wena3ActivitySample stepsSample = new SonyWena3ActivitySampleProvider(getDevice(), db.getDaoSession()).getLatestActivitySample();
+ if(stepsSample != null) {
+ stepLastSyncTime = new Date(stepsSample.getTimestamp() * 1000L);
+ }
+
+ Wena3BehaviorSample behaviorSample = new SonyWena3BehaviorSampleProvider(getDevice(), db.getDaoSession()).getLatestSample();
+ if(behaviorSample != null) {
+ behaviorLastSyncTime = new Date(behaviorSample.getTimestamp());
+ }
+
+ Wena3Vo2Sample vo2Sample = new SonyWena3Vo2SampleProvider(getDevice(), db.getDaoSession()).getLatestSample();
+ if(vo2Sample != null) {
+ vo2LastSyncTime = new Date(vo2Sample.getTimestamp());
+ }
+
+ Wena3EnergySample energySample = new SonyWena3EnergySampleProvider(getDevice(), db.getDaoSession()).getLatestSample();
+ if(energySample != null) {
+ energyLastSyncTime = new Date(energySample.getTimestamp());
+ }
+
+ Wena3CaloriesSample caloriesSample = new SonyWena3CaloriesSampleProvider(getDevice(), db.getDaoSession()).getLatestSample();
+ if(caloriesSample != null) {
+ caloriesLastSyncTime = new Date(caloriesSample.getTimestamp());
+ }
+
+ LOG.info(
+ "Found last sync dates: " +
+ "\nsteps = " + stepLastSyncTime +
+ "\nheart = " + heartLastSyncTime +
+ "\nbehavior = " + behaviorLastSyncTime +
+ "\nvo2 = " + vo2LastSyncTime +
+ "\nstress = " + stressLastSyncTime +
+ "\nenergy = " + energyLastSyncTime +
+ "\ncalories = " + caloriesLastSyncTime +
+ "\nevents = " + eventsLastSyncTime
+ );
+ } catch (Exception e) {
+ LOG.warn("Failed to communicate with DB to find last sync timestamps -- syncing everything:", e);
+ }
+ }
+
+ builder.write(
+ sportsCharacteristic,
+ new ActivitySyncTimePacketTypeA(stepLastSyncTime, heartLastSyncTime, behaviorLastSyncTime, vo2LastSyncTime).toByteArray()
+ );
+
+ builder.write(
+ sportsCharacteristic,
+ new ActivitySyncTimePacketTypeB(stressLastSyncTime, energyLastSyncTime, caloriesLastSyncTime, eventsLastSyncTime).toByteArray()
+ );
+
+ builder.write(sportsCharacteristic, new ActivitySyncStartPacket().toByteArray());
+
+ if(b == null) performImmediately(builder);
+ } catch (IOException e) {
+ LOG.warn("Unable to force request a sync", e);
+ }
+ }
+
+ private void sendWeatherInfo(WeatherReport weather, @Nullable TransactionBuilder b) {
+ try {
+ TransactionBuilder builder = b == null ? performInitialized("updateWeather") : b;
+
+ builder.write(
+ getCharacteristic(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID),
+ weather.toByteArray()
+ );
+
+ if(b == null) performImmediately(builder);
+ } catch (IOException e) {
+ LOG.warn("Unable to send current weather", e);
+ }
+ }
+
+ @Override
+ public void onSetMusicInfo(MusicSpec musicSpec) {
+ StringBuilder sb = new StringBuilder();
+ boolean hasTrackName = musicSpec.track != null && musicSpec.track.trim().length() > 0;
+ boolean hasArtistName = musicSpec.artist != null && musicSpec.artist.trim().length() > 0;
+
+ if(hasTrackName) {
+ sb.append(musicSpec.track.trim());
+ }
+ if(hasArtistName && hasTrackName) {
+ sb.append(" / ");
+ }
+ if(hasArtistName) {
+ sb.append(musicSpec.artist.trim());
+ }
+
+ lastMusicInfo = sb.toString();
+ sendMusicInfo(lastMusicInfo);
+ }
+
+ @Override
+ public void onSetMusicState(MusicStateSpec stateSpec) {
+ if(stateSpec.state == MusicStateSpec.STATE_PLAYING && lastMusicInfo != null) {
+ sendMusicInfo(lastMusicInfo);
+ } else if (stateSpec.state == MusicStateSpec.STATE_STOPPED || stateSpec.state == MusicStateSpec.STATE_PAUSED) {
+ lastMusicInfo = "";
+ sendMusicInfo("");
+ }
+ }
+
+ @Override
+ public void onSetCallState(CallSpec callSpec) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ boolean enableNotifications = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false);
+ boolean enableCalls = prefs.getBoolean(SonyWena3SettingKeys.RECEIVE_CALLS, true);
+ if(!enableCalls || !enableNotifications) {
+ LOG.info("Calls are disabled, ignoring");
+ return;
+ }
+
+ try {
+ TransactionBuilder builder = performInitialized("sendCall");
+
+ if(callSpec.command == CallSpec.CALL_INCOMING) {
+ LedColor led = LedColor.valueOf(prefs.getString(SonyWena3SettingKeys.DEFAULT_CALL_LED_COLOR, LedColor.WHITE.name()).toUpperCase());
+ VibrationKind vibra = VibrationKind.valueOf(prefs.getString(SonyWena3SettingKeys.DEFAULT_CALL_VIBRATION_PATTERN, VibrationKind.CONTINUOUS.name()).toUpperCase());
+ boolean vibraContinuous = false;
+ int vibraRepeats = prefs.getInt(SonyWena3SettingKeys.DEFAULT_CALL_VIBRATION_REPETITION, 0);
+ if(vibraRepeats == 0) {
+ vibraContinuous = true;
+ }
+
+ builder.write(
+ getCharacteristic(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID),
+ new NotificationArrival(
+ NotificationKind.CALL,
+ INCOMING_CALL_ID,
+ callSpec.number,
+ callSpec.name,
+ "",
+ new Date(),
+ new VibrationOptions(vibra, vibraRepeats, vibraContinuous),
+ led,
+ NotificationFlags.NONE
+ ).toByteArray()
+ );
+ } else {
+ builder.write(
+ getCharacteristic(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID),
+ new NotificationRemoval(NotificationKind.CALL, INCOMING_CALL_ID).toByteArray()
+ );
+ }
+
+ performImmediately(builder);
+ } catch (IOException e) {
+ LOG.warn("Unable to send call", e);
+ }
+ }
+
+ @Override
+ public void onNotification(NotificationSpec notificationSpec) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ boolean enableNotifications = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false);
+ if(!enableNotifications) return;
+
+ try {
+ TransactionBuilder builder = performInitialized("sendNotify");
+
+ StringBuilder bodyBuilder = new StringBuilder();
+
+ if(notificationSpec.sender != null && notificationSpec.sender.length() > 0) {
+ bodyBuilder.append(notificationSpec.sender);
+ bodyBuilder.append(":");
+ }
+
+ if(notificationSpec.title != null && notificationSpec.title.length() > 0) {
+ if(bodyBuilder.length() > 0) {
+ bodyBuilder.append("\n");
+ }
+ bodyBuilder.append(notificationSpec.title);
+ bodyBuilder.append(":");
+ }
+
+ if(notificationSpec.subject != null && notificationSpec.subject.length() > 0) {
+ if(bodyBuilder.length() > 0) {
+ bodyBuilder.append("\n");
+ }
+ bodyBuilder.append("- ");
+ bodyBuilder.append(notificationSpec.subject);
+ }
+
+ if(notificationSpec.body != null) {
+ if(bodyBuilder.length() > 0) {
+ bodyBuilder.append("\n");
+ }
+ bodyBuilder.append(notificationSpec.body);
+ }
+
+ String actionLabel = notificationSpec.attachedActions.isEmpty() ? "" :
+ notificationSpec.attachedActions.get(0).title;
+
+ boolean hasAction = !notificationSpec.attachedActions.isEmpty();
+
+ NotificationFlags flags = NotificationFlags.NONE;
+ // TODO: Figure out how actions work
+
+ LedColor led = LedColor.valueOf(prefs.getString(SonyWena3SettingKeys.DEFAULT_LED_COLOR, LedColor.BLUE.name()).toUpperCase());
+ VibrationKind vibra = VibrationKind.valueOf(prefs.getString(SonyWena3SettingKeys.DEFAULT_VIBRATION_PATTERN, VibrationKind.BASIC.name()).toUpperCase());
+ boolean vibraContinuous = false;
+ int vibraRepeats = prefs.getInt(SonyWena3SettingKeys.DEFAULT_VIBRATION_REPETITION, 1);
+
+ if(notificationSpec.sourceAppId != null) {
+ AppSpecificNotificationSetting appSpecificSetting = perAppNotificationSettingsRepository.getSettingsForAppId(notificationSpec.sourceAppId);
+ if(appSpecificSetting != null) {
+ if(appSpecificSetting.getLedPattern() != null) {
+ led = LedColor.valueOf(appSpecificSetting.getLedPattern().toUpperCase());
+ }
+
+ if(appSpecificSetting.getVibrationPattern() != null) {
+ vibra = VibrationKind.valueOf(appSpecificSetting.getVibrationPattern().toUpperCase());
+ }
+
+ if(appSpecificSetting.getVibrationRepetition() != null) {
+ vibraRepeats = Integer.valueOf(appSpecificSetting.getVibrationRepetition());
+ }
+ }
+ }
+
+ if(vibraRepeats == 0) {
+ vibraContinuous = true;
+ }
+
+ builder.write(
+ getCharacteristic(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID),
+ new NotificationArrival(
+ NotificationKind.APP,
+ notificationSpec.getId(),
+ notificationSpec.sourceName,
+ bodyBuilder.toString(),
+ actionLabel,
+ new Date(notificationSpec.when),
+ new VibrationOptions(vibra, vibraRepeats, vibraContinuous),
+ led,
+ flags
+ ).toByteArray()
+ );
+
+ performImmediately(builder);
+ } catch (IOException e) {
+ LOG.warn("Unable to send notification", e);
+ }
+ }
+
+ @Override
+ public void onDeleteNotification(int id) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ boolean enableNotifications = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false);
+ if(!enableNotifications) return;
+
+ try {
+ TransactionBuilder builder = performInitialized("delNotify");
+
+ builder.write(
+ getCharacteristic(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID),
+ new NotificationRemoval(NotificationKind.APP, id).toByteArray()
+ );
+
+ performImmediately(builder);
+ } catch (IOException e) {
+ LOG.warn("Unable to send notification", e);
+ }
+ }
+
+ @Override
+ public void onSendWeather(WeatherSpec weatherSpec) {
+ if(weatherSpec.forecasts.size() < 4) return;
+
+ ArrayList days = new ArrayList<>();
+ // Add today
+ days.add(
+ new WeatherDay(
+ Weather.fromOpenWeatherMap(weatherSpec.currentConditionCode),
+ Weather.fromOpenWeatherMap(weatherSpec.currentConditionCode),
+ weatherSpec.todayMaxTemp,
+ weatherSpec.todayMinTemp
+ )
+ );
+
+ // Add other days
+ for(int i = 0; i < 4; i++) {
+ days.add(WeatherDay.fromSpec(weatherSpec.forecasts.get(i)));
+ }
+
+ WeatherReport report = new WeatherReport(days);
+ sendWeatherInfo(report, null);
+ }
+
+ @Override
+ public void onSetAlarms(ArrayList extends Alarm> alarms) {
+ try {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ TransactionBuilder builder = performInitialized("alarmSetting");
+
+ assert alarms.size() <= SonyWena3Constants.ALARM_SLOTS;
+
+ int wakeupMargin = prefs.getInt(SonyWena3SettingKeys.SMART_WAKEUP_MARGIN_MINUTES,
+ SonyWena3Constants.ALARM_DEFAULT_SMART_WAKEUP_MARGIN_MINUTES);
+
+ for(
+ int i = 0;
+ i < SonyWena3Constants.ALARM_SLOTS;
+ i += AlarmListSettings.MAX_ALARMS_IN_PACKET
+ ) {
+ AlarmListSettings pkt = new AlarmListSettings(new ArrayList<>(), i);
+
+ for(int j = 0; j < AlarmListSettings.MAX_ALARMS_IN_PACKET; j++) {
+ if(i + j < alarms.size()) {
+ Alarm alarm = alarms.get(i + j);
+ SingleAlarmSetting sas = new SingleAlarmSetting(
+ alarm.getEnabled(),
+ (byte) alarm.getRepetition(),
+ alarm.getSmartWakeup() ? wakeupMargin : 0,
+ alarm.getHour(),
+ alarm.getMinute()
+ );
+ pkt.alarms.add(sas);
+ }
+ }
+
+ builder.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ pkt.toByteArray()
+ );
+ }
+
+ performImmediately(builder);
+ } catch (IOException e) {
+ LOG.warn("Unable to send alarms", e);
+ GB.toast("Failed to save alarms", Toast.LENGTH_SHORT, GB.ERROR);
+ }
+ }
+
+ private void sendDisplaySettings(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+
+ String localeString = prefs.getString(DeviceSettingsPreferenceConst.PREF_LANGUAGE, DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO);
+ if (localeString == null || localeString.equals(DeviceSettingsPreferenceConst.PREF_LANGUAGE_AUTO)) {
+ String language = Locale.getDefault().getLanguage();
+ String country = Locale.getDefault().getCountry();
+
+ if (country == null) {
+ country = language;
+ }
+ localeString = language + "_" + country.toUpperCase();
+ }
+ LOG.info("Setting device to locale: " + localeString);
+
+ Language languageCode = Language.ENGLISH;
+
+ switch (localeString.substring(0, 2)) {
+ case "en":
+ languageCode = Language.ENGLISH;
+ break;
+ case "ja":
+ languageCode = Language.JAPANESE;
+ break;
+ }
+ LOG.info("Resolved locale: " + languageCode.name());
+
+ DisplaySetting pkt = new DisplaySetting(
+ prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SCREEN_LIFT_WRIST, false),
+ languageCode,
+ prefs.getInt(DeviceSettingsPreferenceConst.PREF_SCREEN_TIMEOUT, 5),
+ (prefs.getString(DeviceSettingsPreferenceConst.PREF_WEARLOCATION, "left")
+ .equals("left") ? DisplayOrientation.LEFT_HAND : DisplayOrientation.RIGHT_HAND),
+ (prefs.getBoolean(SonyWena3SettingKeys.RICH_DESIGN_MODE, false) ? DisplayDesign.RICH : DisplayDesign.NORMAL),
+ (prefs.getBoolean(SonyWena3SettingKeys.LARGE_FONT_SIZE, false) ? FontSize.LARGE : FontSize.NORMAL),
+ prefs.getBoolean(SonyWena3SettingKeys.WEATHER_IN_STATUSBAR, true)
+ );
+
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ pkt.toByteArray()
+ );
+ }
+
+ private void sendDnDSettings(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ String dndMode = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_OFF);
+ boolean isDndOn = (dndMode != null && dndMode.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SCHEDULED));
+ String start = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START, "22:00");
+ String end = prefs.getString(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END, "06:00");
+
+ Calendar startCalendar = GregorianCalendar.getInstance();
+ Calendar endCalendar = GregorianCalendar.getInstance();
+ DateFormat df = new SimpleDateFormat("HH:mm");
+
+ try {
+ startCalendar.setTime(df.parse(start));
+ endCalendar.setTime(df.parse(end));
+ } catch (ParseException e) {
+ LOG.error("settings error: " + e);
+ }
+
+ byte startH = (byte)startCalendar.get(Calendar.HOUR_OF_DAY);
+ byte startM = (byte)startCalendar.get(Calendar.MINUTE);
+ byte endH = (byte)endCalendar.get(Calendar.HOUR_OF_DAY);
+ byte endM = (byte)endCalendar.get(Calendar.MINUTE);
+
+ DoNotDisturbSettings dndPkt = new DoNotDisturbSettings(isDndOn, startH, startM, endH, endM);
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ dndPkt.toByteArray()
+ );
+ }
+
+ private void sendAutoPowerSettings(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ String autoPowerMode = prefs.getString(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_KIND, DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_OFF);
+ boolean isAutoPowerOffEnabled = (autoPowerMode != null && autoPowerMode.equals(DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_SCHEDULED));
+ String start = prefs.getString(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_START_HHMM, "22:00");
+ String end = prefs.getString(SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_END_HHMM, "06:00");
+
+ Calendar startCalendar = GregorianCalendar.getInstance();
+ Calendar endCalendar = GregorianCalendar.getInstance();
+ DateFormat df = new SimpleDateFormat("HH:mm");
+
+ try {
+ startCalendar.setTime(df.parse(start));
+ endCalendar.setTime(df.parse(end));
+ } catch (ParseException e) {
+ LOG.error("settings error: " + e);
+ }
+
+ byte startH = (byte)startCalendar.get(Calendar.HOUR_OF_DAY);
+ byte startM = (byte)startCalendar.get(Calendar.MINUTE);
+ byte endH = (byte)endCalendar.get(Calendar.HOUR_OF_DAY);
+ byte endM = (byte)endCalendar.get(Calendar.MINUTE);
+
+ AutoPowerOffSettings powerOffPkt = new AutoPowerOffSettings(isAutoPowerOffEnabled, startH, startM, endH, endM);
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ powerOffPkt.toByteArray()
+ );
+ }
+
+ private void sendVibrationSettings(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ boolean smartVibration = prefs.getBoolean(SonyWena3SettingKeys.SMART_VIBRATION, true);
+ VibrationStrength strength = VibrationStrength.valueOf(prefs.getString(SonyWena3SettingKeys.VIBRATION_STRENGTH, VibrationStrength.NORMAL.name()).toUpperCase());
+ VibrationSetting pkt = new VibrationSetting(smartVibration, strength);
+
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ pkt.toByteArray()
+ );
+ }
+
+ private void sendHomeScreenSettings(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ String leftIdName = prefs.getString(SonyWena3SettingKeys.LEFT_HOME_ICON, HomeIconId.MUSIC.name()).toUpperCase();
+ String centerIdName = prefs.getString(SonyWena3SettingKeys.CENTER_HOME_ICON, HomeIconId.PEDOMETER.name()).toUpperCase();
+ String rightIdName = prefs.getString(SonyWena3SettingKeys.RIGHT_HOME_ICON, HomeIconId.CALORIES.name()).toUpperCase();
+
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ new HomeIconOrderSetting(
+ HomeIconId.valueOf(leftIdName),
+ HomeIconId.valueOf(centerIdName),
+ HomeIconId.valueOf(rightIdName)
+ ).toByteArray()
+ );
+ }
+
+ private void sendMenuSettings(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ String[] csv = prefs.getString(SonyWena3SettingKeys.MENU_ICON_CSV_KEY,
+ TextUtils.join(",", getContext().getResources().getStringArray(R.array.prefs_wena3_menu_icons_default_list)))
+ .toUpperCase()
+ .split(",");
+
+ MenuIconSetting menu = new MenuIconSetting();
+
+ for(String iconIdName: csv) {
+ if(!iconIdName.equals(MenuIconId.NONE.name())) {
+ menu.iconList.add(MenuIconId.valueOf(iconIdName));
+ }
+ }
+
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ menu.toByteArray()
+ );
+ }
+
+ private void sendStatusPageSettings(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ StatusPageOrderSetting pageOrderSetting = new StatusPageOrderSetting();
+ String[] csv = prefs.getString(SonyWena3SettingKeys.STATUS_PAGE_CSV_KEY,
+ TextUtils.join(",", getContext().getResources().getStringArray(R.array.prefs_wena3_status_page_default_list)))
+ .toUpperCase()
+ .split(",");
+ for(String idName: csv) {
+ if(!idName.equals(StatusPageId.NONE.name())) {
+ pageOrderSetting.pages.add(StatusPageId.valueOf(idName));
+ }
+ }
+
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ pageOrderSetting.toByteArray()
+ );
+ }
+
+ private void sendActivityGoalSettings(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ ActivityUser user = new ActivityUser();
+ if(user.getYearOfBirth() < 1920) {
+ LOG.error("Device does not support this year of birth");
+ return;
+ }
+ boolean stepsNotification = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION, false);
+
+ GenderSetting gender = user.getGender() == ActivityUser.GENDER_FEMALE ? GenderSetting.FEMALE : GenderSetting.MALE;
+
+ // Maybe we need to set the full birth date?
+ BodyPropertiesSetting bodyPropertiesSetting = new BodyPropertiesSetting(gender, (short)user.getYearOfBirth(), (short)0, (short)1, (short)user.getWeightKg(), (short)user.getHeightCm());
+ GoalStepsSetting stepsSetting = new GoalStepsSetting(stepsNotification, user.getStepsGoal());
+
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ bodyPropertiesSetting.toByteArray()
+ );
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ stepsSetting.toByteArray()
+ );
+ }
+
+ private void sendDayStartHour(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ int hour = prefs.getInt(SonyWena3SettingKeys.DAY_START_HOUR, 6);
+ DayStartHourSetting setting = new DayStartHourSetting(hour);
+
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ setting.toByteArray()
+ );
+ }
+
+ private void sendButtonActions(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ String doubleIdName = prefs.getString(SonyWena3SettingKeys.BUTTON_DOUBLE_PRESS_ACTION, DeviceButtonActionId.NONE.name()).toUpperCase();
+ String longIdName = prefs.getString(SonyWena3SettingKeys.BUTTON_LONG_PRESS_ACTION, DeviceButtonActionId.NONE.name()).toUpperCase();
+ DeviceButtonActionSetting setting = new DeviceButtonActionSetting(
+ DeviceButtonActionId.valueOf(longIdName),
+ DeviceButtonActionId.valueOf(doubleIdName)
+ );
+
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ setting.toByteArray()
+ );
+ }
+
+ private void sendCalendarNotificationToggles(TransactionBuilder b) {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ boolean enableCalendar = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR, false);
+ boolean enableNotifications = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE, false);
+ CalendarNotificationEnableSetting setting = new CalendarNotificationEnableSetting(enableCalendar, enableNotifications);
+
+ b.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ setting.toByteArray()
+ );
+ }
+
+ private void sendAllSettings(TransactionBuilder builder) {
+ sendCurrentTime(builder);
+ builder.write(
+ getCharacteristic(SonyWena3Constants.COMMON_SERVICE_CHARACTERISTIC_CONTROL_UUID),
+ CameraAppTypeSetting.findOut(getContext().getPackageManager()).toByteArray()
+ );
+ sendMenuSettings(builder);
+ sendStatusPageSettings(builder);
+ sendDisplaySettings(builder);
+ sendDnDSettings(builder);
+ sendAutoPowerSettings(builder);
+ sendVibrationSettings(builder);
+ sendHomeScreenSettings(builder);
+ sendActivityGoalSettings(builder);
+ sendDayStartHour(builder);
+ sendButtonActions(builder);
+ sendCalendarNotificationToggles(builder);
+ }
+
+ private void sendAllCalendarEvents(TransactionBuilder b) {
+ try {
+ Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
+ boolean enableCalendar = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR, false);
+
+ TransactionBuilder builder = b == null ? performInitialized("updateCalendarEvents") : b;
+
+ if(!enableCalendar || calendarEvents.isEmpty()) {
+ builder.write(
+ getCharacteristic(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID),
+ CalendarEntry.byteArrayForEmptyEvent((byte) 0, (byte) 0)
+ );
+ }
+ else {
+ int i = 1;
+ int total = Math.min(calendarEvents.size(), 255);
+ for(CalendarEventSpec evt: calendarEvents) {
+ builder.write(
+ getCharacteristic(SonyWena3Constants.NOTIFICATION_SERVICE_CHARACTERISTIC_UUID),
+ new CalendarEntry(
+ new Date(evt.timestamp * 1000L),
+ new Date((evt.timestamp * 1000L) + (evt.durationInSeconds * 1000L)),
+ evt.allDay,
+ (evt.title == null ? "" : evt.title),
+ (evt.location == null ? "" : evt.location),
+ (byte) i,
+ (byte) total
+ ).toByteArray()
+ );
+ if(i == 255) break;
+ else i++;
+ }
+ }
+
+ if(b == null) performImmediately(builder);
+ } catch (IOException e) {
+ LOG.warn("Unable to send calendar events", e);
+ }
+ }
+
+ @Override
+ public void onSetTime() {
+ sendCurrentTime(null);
+ }
+
+ @Override
+ public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
+ calendarEvents.add(calendarEventSpec);
+ sendAllCalendarEvents(null);
+ }
+
+ @Override
+ public void onDeleteCalendarEvent(byte type, long id) {
+ for(CalendarEventSpec evt : calendarEvents) {
+ if(evt.type == type && evt.id == id) {
+ calendarEvents.remove(evt);
+ }
+ }
+ sendAllCalendarEvents(null);
+ }
+
+ @Override
+ public void onSendConfiguration(String config) {
+ try {
+ TransactionBuilder builder = performInitialized("sendConfig");
+ switch (config) {
+ case SonyWena3SettingKeys.STATUS_PAGE_CSV_KEY:
+ sendStatusPageSettings(builder);
+ break;
+
+ case SonyWena3SettingKeys.MENU_ICON_CSV_KEY:
+ sendMenuSettings(builder);
+ break;
+
+ case DeviceSettingsPreferenceConst.PREF_SCREEN_LIFT_WRIST:
+ case DeviceSettingsPreferenceConst.PREF_LANGUAGE:
+ case DeviceSettingsPreferenceConst.PREF_SCREEN_TIMEOUT:
+ case DeviceSettingsPreferenceConst.PREF_WEARLOCATION:
+ case SonyWena3SettingKeys.RICH_DESIGN_MODE:
+ case SonyWena3SettingKeys.LARGE_FONT_SIZE:
+ case SonyWena3SettingKeys.WEATHER_IN_STATUSBAR:
+ sendDisplaySettings(builder);
+ break;
+
+ case SonyWena3SettingKeys.SMART_WAKEUP_MARGIN_MINUTES:
+ // Resend alarms
+ onSetAlarms(new ArrayList<>(DBHelper.getAlarms(gbDevice)));
+ break;
+
+ case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO:
+ case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_END:
+ case DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO_START:
+ sendDnDSettings(builder);
+ break;
+
+ case SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_KIND:
+ case SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_START_HHMM:
+ case SonyWena3SettingKeys.AUTO_POWER_SCHEDULE_END_HHMM:
+ sendAutoPowerSettings(builder);
+ break;
+
+ case SonyWena3SettingKeys.VIBRATION_STRENGTH:
+ case SonyWena3SettingKeys.SMART_VIBRATION:
+ sendVibrationSettings(builder);
+ break;
+
+ case SonyWena3SettingKeys.LEFT_HOME_ICON:
+ case SonyWena3SettingKeys.CENTER_HOME_ICON:
+ case SonyWena3SettingKeys.RIGHT_HOME_ICON:
+ sendHomeScreenSettings(builder);
+ break;
+
+ case ActivityUser.PREF_USER_YEAR_OF_BIRTH:
+ case ActivityUser.PREF_USER_GENDER:
+ case ActivityUser.PREF_USER_HEIGHT_CM:
+ case ActivityUser.PREF_USER_WEIGHT_KG:
+ case ActivityUser.PREF_USER_STEPS_GOAL:
+ case DeviceSettingsPreferenceConst.PREF_USER_FITNESS_GOAL_NOTIFICATION:
+ sendActivityGoalSettings(builder);
+ break;
+
+ case SonyWena3SettingKeys.DAY_START_HOUR:
+ sendDayStartHour(builder);
+ break;
+
+ case SonyWena3SettingKeys.BUTTON_DOUBLE_PRESS_ACTION:
+ case SonyWena3SettingKeys.BUTTON_LONG_PRESS_ACTION:
+ sendButtonActions(builder);
+ break;
+
+ case DeviceSettingsPreferenceConst.PREF_NOTIFICATION_ENABLE:
+ case DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR:
+ sendCalendarNotificationToggles(builder);
+ sendAllCalendarEvents(builder);
+ break;
+
+ default:
+ LOG.warn("Unsupported setting %s", config);
+ return;
+ }
+
+ performImmediately(builder);
+ } catch(Exception e) {
+ GB.toast("Failed to send settings update", Toast.LENGTH_SHORT, GB.ERROR);
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/Wena3Packetable.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/Wena3Packetable.java
new file mode 100644
index 000000000..124a1c559
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/Wena3Packetable.java
@@ -0,0 +1,21 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol;
+
+public interface Wena3Packetable {
+ byte[] toByteArray();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketCrc.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketCrc.java
new file mode 100644
index 000000000..89f433ed3
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketCrc.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic;
+
+
+public class ActivityPacketCrc {
+ private static final int CRC_INITIAL_VALUE = 65535;
+ private static final int[] CRC_TABLE = new int[] {
+ 0x0000, 0x8005, 0x800f, 0x000a, 0x801b,
+ 0x001e, 0x0014, 0x8011, 0x8033, 0x0036,
+ 0x003c, 0x8039, 0x0028, 0x802d, 0x8027,
+ 0x0022, 0x8063, 0x0066, 0x006c, 0x8069,
+ 0x0078, 0x807d, 0x8077, 0x0072, 0x0050,
+ 0x8055, 0x805f, 0x005a, 0x804b, 0x004e,
+ 0x0044, 0x8041, 0x80c3, 0x00c6, 0x00cc,
+ 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2,
+ 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb,
+ 0x00ee, 0x00e4, 0x80e1, 0x00a0, 0x80a5,
+ 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4,
+ 0x80b1, 0x8093, 0x0096, 0x009c, 0x8099,
+ 0x0088, 0x808d, 0x8087, 0x0082, 0x8183,
+ 0x0186, 0x018c, 0x8189, 0x0198, 0x819d,
+ 0x8197, 0x0192, 0x01b0, 0x81b5, 0x81bf,
+ 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1,
+ 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb,
+ 0x01fe, 0x01f4, 0x81f1, 0x81d3, 0x01d6,
+ 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7,
+ 0x01c2, 0x0140, 0x8145, 0x814f, 0x014a,
+ 0x815b, 0x015e, 0x0154, 0x8151, 0x8173,
+ 0x0176, 0x017c, 0x8179, 0x0168, 0x816d,
+ 0x8167, 0x0162, 0x8123, 0x0126, 0x012c,
+ 0x8129, 0x0138, 0x813d, 0x8137, 0x0132,
+ 0x0110, 0x8115, 0x811f, 0x011a, 0x810b,
+ 0x010e, 0x0104, 0x8101, 0x8303, 0x0306,
+ 0x030c, 0x8309, 0x0318, 0x831d, 0x8317,
+ 0x0312, 0x0330, 0x8335, 0x833f, 0x033a,
+ 0x832b, 0x032e, 0x0324, 0x8321, 0x0360,
+ 0x8365, 0x836f, 0x036a, 0x837b, 0x037e,
+ 0x0374, 0x8371, 0x8353, 0x0356, 0x035c,
+ 0x8359, 0x0348, 0x834d, 0x8347, 0x0342,
+ 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db,
+ 0x03de, 0x03d4, 0x83d1, 0x83f3, 0x03f6,
+ 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7,
+ 0x03e2, 0x83a3, 0x03a6, 0x03ac, 0x83a9,
+ 0x03b8, 0x83bd, 0x83b7, 0x03b2, 0x0390,
+ 0x8395, 0x839f, 0x039a, 0x838b, 0x038e,
+ 0x0384, 0x8381, 0x0280, 0x8285, 0x828f,
+ 0x028a, 0x829b, 0x029e, 0x0294, 0x8291,
+ 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8,
+ 0x82ad, 0x82a7, 0x02a2, 0x82e3, 0x02e6,
+ 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7,
+ 0x02f2, 0x02d0, 0x82d5, 0x82df, 0x02da,
+ 0x82cb, 0x02ce, 0x02c4, 0x82c1, 0x8243,
+ 0x0246, 0x024c, 0x8249, 0x0258, 0x825d,
+ 0x8257, 0x0252, 0x0270, 0x8275, 0x827f,
+ 0x027a, 0x826b, 0x026e, 0x0264, 0x8261,
+ 0x0220, 0x8225, 0x822f, 0x022a, 0x823b,
+ 0x023e, 0x0234, 0x8231, 0x8213, 0x0216,
+ 0x021c, 0x8219, 0x0208, 0x820d, 0x8207,
+ 0x0202
+ };
+
+ private int result = CRC_INITIAL_VALUE;
+
+ public void next(byte i) {
+ result = CRC_TABLE[((i & 255) ^ (result >>> 8))] ^ ((result << 8) & 65535);
+ }
+
+ public void next(byte[] b) {
+ for(byte v: b) next(v);
+ }
+
+ public int getResult() {
+ return result;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketParser.java
new file mode 100644
index 000000000..fce5bf6ce
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivityPacketParser.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncDataPacket;
+
+public interface ActivityPacketParser {
+ /// Return `true` if further data packets should be accepted by this parser.
+ /// Make sure to configure internal state correctly to prepare for incoming data if returning `true`.
+ /// Otherwise return `false`.
+ boolean parseHeader(ActivitySyncDataPacket packet, GBDevice sourceDevice);
+ void parsePacket(ActivitySyncDataPacket packet, GBDevice sourceDevice);
+ void finishReceiving(GBDevice device);
+ void reset();
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivitySyncPacketProcessor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivitySyncPacketProcessor.java
new file mode 100644
index 000000000..e689f6997
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/ActivitySyncPacketProcessor.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncDataPacket;
+
+public class ActivitySyncPacketProcessor {
+ private static final int RESET_SEQ_NO = 0xFF;
+ private static final int MAX_SEQ_NO = 0x7F;
+ private static final Logger LOG = LoggerFactory.getLogger(ActivitySyncPacketProcessor.class);
+
+ private ActivityPacketParser currentParser = null;
+ private final List parsers = new ArrayList<>();
+ private int currentSeqNo = RESET_SEQ_NO;
+ private boolean haveHeader = false;
+
+ public ActivitySyncPacketProcessor() {}
+
+ public void registerParser(ActivityPacketParser parser) {
+ parsers.add(parser);
+ }
+
+ public void receivePacket(ActivitySyncDataPacket packet, GBDevice device) {
+ if(packet.sequenceNo == RESET_SEQ_NO) {
+ LOG.info("Initial packet received, off we go with a sync!");
+ currentSeqNo = RESET_SEQ_NO;
+ resetAll();
+ // Do NOT return here: the initial packet is still a packet!
+ }
+
+ if(packet.sequenceNo != currentSeqNo) {
+ LOG.error("There was packet loss (skip "+currentSeqNo+" to "+packet.sequenceNo+")");
+ finalizeCurrentParserIfNeeded(device);
+ return;
+ } else {
+ if(currentSeqNo == MAX_SEQ_NO || currentSeqNo == RESET_SEQ_NO) {
+ currentSeqNo = 0;
+ } else {
+ currentSeqNo ++;
+ }
+ }
+
+ if(!packet.isCrcValid) {
+ LOG.error("Received packet has invalid CRC");
+ return;
+ }
+
+ switch(packet.type) {
+ case HEADER:
+ haveHeader = true;
+ finalizeCurrentParserIfNeeded(device);
+ for(ActivityPacketParser parser: parsers) {
+ if(parser.parseHeader(packet, device)) {
+ currentParser = parser;
+ break;
+ }
+ }
+ if(currentParser == null) {
+ LOG.warn("No parsers can understand " + packet);
+ }
+ break;
+
+ case DATA:
+ if(currentParser != null) {
+ currentParser.parsePacket(packet, device);
+ } else {
+ if(!haveHeader) {
+ LOG.warn("DATA arrived before HEADER: dropped " + packet);
+ } else {
+ LOG.warn("No parser known: dropped data packet " + packet);
+ }
+ }
+ break;
+
+ case FINISH:
+ LOG.info("End of transmission received");
+ finalizeCurrentParserIfNeeded(device);
+ break;
+ }
+ }
+
+ public void resetAll() {
+ currentParser = null;
+ for(ActivityPacketParser parser: parsers) parser.reset();
+ }
+
+ private void finalizeCurrentParserIfNeeded(GBDevice device) {
+ if(currentParser != null) {
+ currentParser.finishReceiving(device);
+ currentParser = null;
+ }
+ haveHeader = false;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/StepsSampleCollection.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/StepsSampleCollection.java
new file mode 100644
index 000000000..142ac3994
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/StepsSampleCollection.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic;
+
+import java.util.Date;
+import java.util.List;
+
+public class StepsSampleCollection {
+ public final Date startDate;
+ public final List everyMinuteCount;
+
+ public StepsSampleCollection(Date startDate, List everyMinuteCount) {
+ this.startDate = startDate;
+ this.everyMinuteCount = everyMinuteCount;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/BehaviorPacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/BehaviorPacketParser.java
new file mode 100644
index 000000000..bd58d9e5b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/BehaviorPacketParser.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleCombiner;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3BehaviorSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3BehaviorSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.BehaviorSample;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
+
+public class BehaviorPacketParser extends SamplePacketParser {
+ private static final Logger LOG = LoggerFactory.getLogger(BehaviorPacketParser.class);
+ public BehaviorPacketParser() {
+ super(0x02);
+ }
+
+ @Override
+ boolean tryExtractingMetadataFromHeaderBuffer(ByteBuffer buffer) {
+ return true;
+ }
+
+ @Override
+ BehaviorSample takeSampleFromBuffer(ByteBuffer buffer) {
+ // Entry structure:
+ // - 1b type
+ // - 4b padding?
+ // - 4b start date
+ // - 4b end date
+ int id = (buffer.get() & 0xFF);
+ if(id < BehaviorSample.Type.LUT.length) {
+ BehaviorSample.Type type = BehaviorSample.Type.LUT[id];
+ buffer.position(buffer.position()+4);
+ Date start = TimeUtil.wenaTimeToDate(buffer.getInt());
+ Date end = TimeUtil.wenaTimeToDate(buffer.getInt());
+ return new BehaviorSample(start, end, type);
+ }
+ return null;
+ }
+
+ @Override
+ boolean canTakeSampleFromBuffer(ByteBuffer buffer) {
+ return buffer.remaining() >= 13;
+ }
+
+ @Override
+ public void finishReceiving(GBDevice device) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ SonyWena3BehaviorSampleProvider sampleProvider = new SonyWena3BehaviorSampleProvider(device, db.getDaoSession());
+ Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+ Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+ List samples = new ArrayList<>();
+
+ for(BehaviorSample rawSample: accumulator) {
+ Wena3BehaviorSample gbSample = new Wena3BehaviorSample();
+ gbSample.setDeviceId(deviceId);
+ gbSample.setUserId(userId);
+ gbSample.setTimestamp(rawSample.start.getTime());
+ gbSample.setTimestampFrom(rawSample.start.getTime());
+ gbSample.setTimestampTo(rawSample.end.getTime());
+ gbSample.setRawKind(rawSample.type.ordinal());
+ samples.add(gbSample);
+ }
+ sampleProvider.addSamples(samples);
+
+ if(!accumulator.isEmpty()) {
+ SonyWena3ActivitySampleProvider activitySampleProvider = new SonyWena3ActivitySampleProvider(device, db.getDaoSession());
+ SonyWena3ActivitySampleCombiner combiner = new SonyWena3ActivitySampleCombiner(activitySampleProvider);
+ combiner.overlayBehaviorStartingAt(accumulator.get(0).start, sampleProvider);
+ }
+ } catch (Exception e) {
+ LOG.error("Error acquiring database for recording Behavior samples", e);
+ }
+
+ // Finally clean up the parser
+ super.finishReceiving(device);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/CaloriesPacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/CaloriesPacketParser.java
new file mode 100644
index 000000000..acd30be03
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/CaloriesPacketParser.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3CaloriesSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3CaloriesSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class CaloriesPacketParser extends LinearSamplePacketParser {
+ private static final Logger LOG = LoggerFactory.getLogger(CaloriesPacketParser.class);
+ public CaloriesPacketParser() {
+ super(0x06, OneBytePerSamplePacketParser.ONE_MINUTE_IN_MS);
+ }
+
+ @Override
+ Integer takeSampleFromBuffer(ByteBuffer buffer) {
+ return (buffer.getShort() & 65535);
+ }
+
+ @Override
+ boolean canTakeSampleFromBuffer(ByteBuffer buffer) {
+ return buffer.remaining() >= 2;
+ }
+ @Override
+ public void finishReceiving(GBDevice device) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ SonyWena3CaloriesSampleProvider sampleProvider = new SonyWena3CaloriesSampleProvider(device, db.getDaoSession());
+ Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+ Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+
+ int i = 0;
+ List samples = new ArrayList<>();
+
+ for(int rawSample: accumulator) {
+ Date currentSampleDate = timestampOfSampleAtIndex(i);
+
+ Wena3CaloriesSample gbSample = new Wena3CaloriesSample();
+ gbSample.setDeviceId(deviceId);
+ gbSample.setUserId(userId);
+ gbSample.setTimestamp(currentSampleDate.getTime());
+ gbSample.setCalories(rawSample);
+ samples.add(gbSample);
+
+ i++;
+ }
+
+ sampleProvider.addSamples(samples);
+ } catch (Exception e) {
+ LOG.error("Error acquiring database for recording Calories samples", e);
+ }
+
+ // Finally clean up the parser
+ super.finishReceiving(device);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/EnergyPacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/EnergyPacketParser.java
new file mode 100644
index 000000000..930a1c8bc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/EnergyPacketParser.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3EnergySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3EnergySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class EnergyPacketParser extends OneBytePerSamplePacketParser {
+ private static final Logger LOG = LoggerFactory.getLogger(EnergyPacketParser.class);
+ private static final int ENERGY_PKT_MARKER = 0x05;
+ public EnergyPacketParser() {
+ super(ENERGY_PKT_MARKER, ONE_MINUTE_IN_MS);
+ }
+ @Override
+ public void finishReceiving(GBDevice device) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ SonyWena3EnergySampleProvider sampleProvider = new SonyWena3EnergySampleProvider(device, db.getDaoSession());
+ Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+ Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+ List samples = new ArrayList<>();
+ int i = 0;
+
+ for(int rawSample: accumulator) {
+ Date currentSampleDate = timestampOfSampleAtIndex(i);
+
+ Wena3EnergySample gbSample = new Wena3EnergySample();
+ gbSample.setDeviceId(deviceId);
+ gbSample.setUserId(userId);
+ gbSample.setTimestamp(currentSampleDate.getTime());
+ gbSample.setEnergy(rawSample);
+ samples.add(gbSample);
+
+ i++;
+ }
+ sampleProvider.addSamples(samples);
+ } catch (Exception e) {
+ LOG.error("Error acquiring database for recording Energy samples", e);
+ }
+
+ // Finally clean up the parser
+ super.finishReceiving(device);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/HeartRatePacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/HeartRatePacketParser.java
new file mode 100644
index 000000000..2fab70efc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/HeartRatePacketParser.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleCombiner;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3HeartRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3HeartRateSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class HeartRatePacketParser extends OneBytePerSamplePacketParser {
+ private static final Logger LOG = LoggerFactory.getLogger(HeartRatePacketParser.class);
+ private static final int HEART_PKT_MARKER = 0x01;
+ public HeartRatePacketParser() {
+ super(HEART_PKT_MARKER, ONE_MINUTE_IN_MS);
+ }
+
+ @Override
+ public void finishReceiving(GBDevice device) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ SonyWena3HeartRateSampleProvider sampleProvider = new SonyWena3HeartRateSampleProvider(device, db.getDaoSession());
+ Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+ Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+ List samples = new ArrayList<>();
+
+ Date currentSampleDate = startDate;
+ int i = 0;
+ for(int rawSample: accumulator) {
+ Wena3HeartRateSample gbSample = new Wena3HeartRateSample();
+ gbSample.setDeviceId(deviceId);
+ gbSample.setUserId(userId);
+ gbSample.setTimestamp(currentSampleDate.getTime());
+ gbSample.setHeartRate(rawSample);
+ samples.add(gbSample);
+
+ i++;
+ currentSampleDate = timestampOfSampleAtIndex(i);
+ }
+
+ sampleProvider.addSamples(samples);
+
+ if(!accumulator.isEmpty()) {
+ SonyWena3ActivitySampleProvider activitySampleProvider = new SonyWena3ActivitySampleProvider(device, db.getDaoSession());
+ SonyWena3ActivitySampleCombiner combiner = new SonyWena3ActivitySampleCombiner(activitySampleProvider);
+ combiner.overlayHeartRateStartingAt(startDate, sampleProvider);
+ }
+ } catch (Exception e) {
+ LOG.error("Error acquiring database for recording heart rate samples", e);
+ }
+
+ // Finally clean up the parser
+ super.finishReceiving(device);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/LinearSamplePacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/LinearSamplePacketParser.java
new file mode 100644
index 000000000..d570cea32
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/LinearSamplePacketParser.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
+
+abstract class LinearSamplePacketParser extends SamplePacketParser {
+ private static final Logger LOG = LoggerFactory.getLogger(LinearSamplePacketParser.class);
+ private int msBetweenSamples = 0;
+ public Date startDate = null;
+
+ public LinearSamplePacketParser(int headerMarker, int sampleDistanceInMs) {
+ super(headerMarker);
+ msBetweenSamples = sampleDistanceInMs;
+ reset();
+ }
+
+ @Override
+ public void finishReceiving(GBDevice device) {
+ Date estimatedEndDate = new Date(startDate.getTime() + ((long) accumulator.size() * msBetweenSamples));
+ LOG.info("Finished collecting "+accumulator.size()+" samples over "+startDate+" ~ "+estimatedEndDate);
+ super.finishReceiving(device);
+ }
+
+ @Override
+ boolean tryExtractingMetadataFromHeaderBuffer(ByteBuffer buffer) {
+ if(buffer.remaining() < 4) {
+ LOG.error("Received ASDP header is too short");
+ return false;
+ }
+
+ startDate = TimeUtil.wenaTimeToDate(buffer.getInt());
+ return true;
+ }
+
+ public Date timestampOfSampleAtIndex(int index) {
+ assert startDate != null;
+ if(index == 0) return startDate;
+ return new Date(startDate.getTime() + (long) msBetweenSamples * index);
+ }
+
+ abstract Sample takeSampleFromBuffer(ByteBuffer buffer);
+ abstract boolean canTakeSampleFromBuffer(ByteBuffer buffer);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/OneBytePerSamplePacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/OneBytePerSamplePacketParser.java
new file mode 100644
index 000000000..2ed004018
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/OneBytePerSamplePacketParser.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import java.nio.ByteBuffer;
+
+public class OneBytePerSamplePacketParser extends LinearSamplePacketParser {
+ public static final int ONE_MINUTE_IN_MS = 60_000; // 1 sample per minute it seems
+
+ public OneBytePerSamplePacketParser(int headerMarker, int sampleDistanceInMs) {
+ super(headerMarker, sampleDistanceInMs);
+ }
+
+ @Override
+ Integer takeSampleFromBuffer(ByteBuffer buffer) {
+ return Integer.valueOf(buffer.get() & 0xFF);
+ }
+
+ @Override
+ boolean canTakeSampleFromBuffer(ByteBuffer buffer) {
+ return buffer.remaining() > 0;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/SamplePacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/SamplePacketParser.java
new file mode 100644
index 000000000..339e3d715
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/SamplePacketParser.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.ActivityPacketParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.ActivitySyncDataPacket;
+
+abstract class SamplePacketParser implements ActivityPacketParser {
+ private static final Logger LOG = LoggerFactory.getLogger(SamplePacketParser.class);
+ private final int headerMarker;
+ public List accumulator = new ArrayList<>();
+ private enum State {
+ READY, RECEIVING, FINISHED
+ }
+ private State currentState = State.READY;
+
+ public SamplePacketParser(int headerMarker) {
+ this.headerMarker = headerMarker;
+ reset();
+ }
+
+ @Override
+ public void reset() {
+ accumulator = new ArrayList<>();
+ currentState = State.READY;
+ }
+ @Override
+ public boolean parseHeader(ActivitySyncDataPacket packet, GBDevice sourceDevice) {
+ assert packet.isCrcValid;
+ assert packet.type == ActivitySyncDataPacket.PacketType.HEADER;
+
+ ByteBuffer buf = packet.dataBuffer();
+
+ int type = buf.get();
+ if(type != this.headerMarker) {
+ LOG.debug("Received ASDP with marker "+type+", not expected type");
+ return false;
+ }
+ if(currentState != State.READY)
+ return false;
+
+ if(!tryExtractingMetadataFromHeaderBuffer(buf)) {
+ return false;
+ }
+
+ currentState = State.RECEIVING;
+ LOG.info("Ready to receive packets");
+
+ return true;
+ }
+
+ @Override
+ public void parsePacket(ActivitySyncDataPacket packet, GBDevice sourceDevice) {
+ assert currentState == State.RECEIVING;
+ assert packet.isCrcValid;
+ assert packet.type == ActivitySyncDataPacket.PacketType.DATA;
+
+ ByteBuffer buf = packet.dataBuffer();
+ while(canTakeSampleFromBuffer(buf))
+ accumulator.add(takeSampleFromBuffer(buf));
+
+ LOG.info("Accumulated "+accumulator.size()+" samples");
+ }
+
+ @Override
+ public void finishReceiving(GBDevice device) {
+ assert currentState == State.RECEIVING;
+ currentState = State.FINISHED;
+
+ reset();
+ }
+
+ abstract Sample takeSampleFromBuffer(ByteBuffer buffer);
+ abstract boolean canTakeSampleFromBuffer(ByteBuffer buffer);
+ abstract boolean tryExtractingMetadataFromHeaderBuffer(ByteBuffer buffer);
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StepsPacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StepsPacketParser.java
new file mode 100644
index 000000000..9856aa03e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StepsPacketParser.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleCombiner;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3ActivitySampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3BehaviorSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3HeartRateSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+
+public class StepsPacketParser extends OneBytePerSamplePacketParser {
+ private static final Logger LOG = LoggerFactory.getLogger(StepsPacketParser.class);
+ private static final int STEPS_PKT_MARKER = 0x00;
+ public StepsPacketParser() {
+ super(STEPS_PKT_MARKER, ONE_MINUTE_IN_MS);
+ }
+
+ @Override
+ public void finishReceiving(GBDevice device) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ SonyWena3ActivitySampleProvider sampleProvider = new SonyWena3ActivitySampleProvider(device, db.getDaoSession());
+ Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+ Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+ List samples = new ArrayList<>();
+ Date currentSampleDate = startDate;
+ int i = 0;
+ for(int rawSample: accumulator) {
+ Wena3ActivitySample gbSample = new Wena3ActivitySample();
+ gbSample.setDeviceId(deviceId);
+ gbSample.setUserId(userId);
+ gbSample.setTimestamp((int)(currentSampleDate.getTime() / 1000L));
+ gbSample.setSteps(rawSample);
+ samples.add(gbSample);
+
+ i++;
+ currentSampleDate = timestampOfSampleAtIndex(i);
+ }
+ sampleProvider.addGBActivitySamples(samples.toArray(new Wena3ActivitySample[samples.size()]));
+
+ SonyWena3ActivitySampleCombiner combiner = new SonyWena3ActivitySampleCombiner(sampleProvider);
+
+ SonyWena3BehaviorSampleProvider behaviorSampleProvider = new SonyWena3BehaviorSampleProvider(device, db.getDaoSession());
+ combiner.overlayBehaviorStartingAt(startDate, behaviorSampleProvider);
+
+ SonyWena3HeartRateSampleProvider heartRateSampleProvider = new SonyWena3HeartRateSampleProvider(device, db.getDaoSession());
+ combiner.overlayHeartRateStartingAt(startDate, heartRateSampleProvider);
+ } catch (Exception e) {
+ LOG.error("Error acquiring database for recording steps samples", e);
+ }
+
+ // Finally clean up the parser
+ super.finishReceiving(device);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StressPacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StressPacketParser.java
new file mode 100644
index 000000000..164eacc97
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/StressPacketParser.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3StressSampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3StressSample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.StressSample;
+
+public class StressPacketParser extends OneBytePerSamplePacketParser {
+ private static final Logger LOG = LoggerFactory.getLogger(StressPacketParser.class);
+ private static final int STRESS_PKT_MARKER = 0x04;
+ public StressPacketParser() {
+ super(STRESS_PKT_MARKER, ONE_MINUTE_IN_MS);
+ }
+ @Override
+ Integer takeSampleFromBuffer(ByteBuffer buffer) {
+ // In this one we actually need signed values, hence the override
+ return Integer.valueOf(buffer.get());
+ }
+ @Override
+ public void finishReceiving(GBDevice device) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ SonyWena3StressSampleProvider sampleProvider = new SonyWena3StressSampleProvider(device, db.getDaoSession());
+ Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+ Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+ List samples = new ArrayList<>();
+
+ Date currentSampleDate = startDate;
+ int i = 0;
+ for(int rawSample: accumulator) {
+ if(rawSample >= -100 && rawSample <= 100) {
+ Wena3StressSample gbSample = new Wena3StressSample();
+ gbSample.setDeviceId(deviceId);
+ gbSample.setUserId(userId);
+ gbSample.setTimestamp(currentSampleDate.getTime());
+ gbSample.setStress(rawSample);
+ gbSample.setType(StressSample.Type.AUTOMATIC);
+ samples.add(gbSample);
+ } else {
+ LOG.debug("Discard stress value as out of range: " + rawSample);
+ }
+
+ i++;
+ currentSampleDate = timestampOfSampleAtIndex(i);
+ }
+
+ sampleProvider.addSamples(samples);
+ } catch (Exception e) {
+ LOG.error("Error acquiring database for recording stress samples", e);
+ }
+
+ // Finally clean up the parser
+ super.finishReceiving(device);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/Vo2MaxPacketParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/Vo2MaxPacketParser.java
new file mode 100644
index 000000000..2afaf6e18
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/logic/parsers/Vo2MaxPacketParser.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.parsers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Vo2SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.entities.Wena3Vo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity.Vo2MaxSample;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
+
+public class Vo2MaxPacketParser extends SamplePacketParser {
+ private static final Logger LOG = LoggerFactory.getLogger(Vo2MaxPacketParser.class);
+ public Vo2MaxPacketParser() {
+ super(0x03);
+ }
+
+ @Override
+ Vo2MaxSample takeSampleFromBuffer(ByteBuffer buffer) {
+ Date ts = TimeUtil.wenaTimeToDate(buffer.getInt());
+ int value = buffer.get() & 0xFF;
+ return new Vo2MaxSample(ts, value);
+ }
+
+ @Override
+ boolean canTakeSampleFromBuffer(ByteBuffer buffer) {
+ return buffer.remaining() >= 5;
+ }
+
+ @Override
+ boolean tryExtractingMetadataFromHeaderBuffer(ByteBuffer buffer) {
+ return true;
+ }
+
+ @Override
+ public void finishReceiving(GBDevice device) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ SonyWena3Vo2SampleProvider sampleProvider = new SonyWena3Vo2SampleProvider(device, db.getDaoSession());
+ Long userId = DBHelper.getUser(db.getDaoSession()).getId();
+ Long deviceId = DBHelper.getDevice(device, db.getDaoSession()).getId();
+ List samples = new ArrayList<>();
+ Date currentSampleDate = null;
+ int currentDateDatapoint = 0;
+
+ for(Vo2MaxSample rawSample: accumulator) {
+ if(currentSampleDate == null || currentSampleDate != rawSample.timestamp) {
+ currentDateDatapoint = 0;
+ currentSampleDate = rawSample.timestamp;
+ } else {
+ currentDateDatapoint ++;
+ }
+ Wena3Vo2Sample gbSample = new Wena3Vo2Sample();
+ gbSample.setDeviceId(deviceId);
+ gbSample.setUserId(userId);
+ gbSample.setTimestamp(currentSampleDate.getTime());
+ gbSample.setDatapoint(currentDateDatapoint);
+ gbSample.setVo2(rawSample.value);
+ samples.add(gbSample);
+ }
+ sampleProvider.addSamples(samples);
+ } catch (Exception e) {
+ LOG.error("Error acquiring database for recording Vo2 samples", e);
+ }
+
+ // Finally clean up the parser
+ super.finishReceiving(device);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncDataPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncDataPacket.java
new file mode 100644
index 000000000..0979ded18
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncDataPacket.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.logic.ActivityPacketCrc;
+
+public class ActivitySyncDataPacket {
+ public enum PacketType {
+ HEADER,
+ DATA,
+ FINISH;
+
+ static final PacketType[] LUT = new PacketType[] { HEADER, DATA, FINISH };
+ }
+
+ public final int sequenceNo;
+ public final PacketType type;
+ public int crc;
+ public int expectedCrc;
+ public boolean isCrcValid;
+ public final byte[] data;
+
+ public ActivitySyncDataPacket(byte[] packet) {
+ /*
+ 1byte [seqno OR FF=RESET]
+ 2byte [crc16]
+ 1byte [mark: 00=head, 01=data, 02=end]
+ IF HEAD
+ 1byte [type: 00=Steps 01=Heart 02=Behavior 03=Vo2 04=Stress 05=BodyEnergy 06=Calories 07=???]
+ (usually) 4byte [WenaDate start ts]
+ IF DATA
+ (samples of appropriate [type]?)
+ IF END
+ (reception completed!)
+ */
+ ByteBuffer buf = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
+ sequenceNo = Integer.valueOf(buf.get() & 0xFF);
+ crc = buf.getShort();
+ crc &= 0xFFFF;
+ type = PacketType.LUT[buf.get()];
+ data = new byte[buf.remaining()];
+ buf.get(data);
+ checkCrc();
+ }
+
+ private void checkCrc() {
+ ActivityPacketCrc calc = new ActivityPacketCrc();
+ ByteBuffer tmp = ByteBuffer.allocate(data.length + 1).put((byte)type.ordinal()).put(data);
+ calc.next(tmp.array());
+ expectedCrc = calc.getResult();
+ isCrcValid = (expectedCrc == crc);
+ }
+
+
+ @NonNull
+ public String toString() {
+ return String.format("", type.ordinal(), sequenceNo, crc, isCrcValid ? "yes" : String.format("NO [expected %x]", expectedCrc), toHexString(data));
+ }
+
+ private static String toHexString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ if (bytes != null)
+ for (byte b:bytes) {
+ final String hexString = Integer.toHexString(b & 0xff);
+ if(hexString.length()==1)
+ sb.append('0');
+ sb.append(hexString).append(' ');
+ }
+ return sb.toString().toUpperCase();
+ }
+
+ public ByteBuffer dataBuffer() {
+ return ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncStartPacket.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncStartPacket.java
new file mode 100644
index 000000000..54203b32b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncStartPacket.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class ActivitySyncStartPacket implements Wena3Packetable {
+ public ActivitySyncStartPacket() {
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return new byte[] { (byte) 0xFF };
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTemplate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTemplate.java
new file mode 100644
index 000000000..020ec45de
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTemplate.java
@@ -0,0 +1,76 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
+
+import androidx.annotation.Nullable;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
+
+abstract class ActivitySyncTimePacketTemplate implements Wena3Packetable {
+ public final byte header;
+
+ @Nullable
+ public final Date date1;
+ @Nullable
+ public final Date date2;
+ @Nullable
+ public final Date date3;
+ @Nullable
+ public final Date date4;
+
+ public ActivitySyncTimePacketTemplate(byte header, @Nullable Date date1, @Nullable Date date2, @Nullable Date date3, @Nullable Date date4) {
+ this.header = header;
+ this.date1 = date1;
+ this.date2 = date2;
+ this.date3 = date3;
+ this.date4 = date4;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ ByteBuffer buf = ByteBuffer.allocate(17)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put(header);
+ if(date1 == null) {
+ buf.putInt(0);
+ } else {
+ buf.putInt(TimeUtil.dateToWenaTime(date1));
+ }
+ if(date2 == null) {
+ buf.putInt(0);
+ } else {
+ buf.putInt(TimeUtil.dateToWenaTime(date2));
+ }
+ if(date3 == null) {
+ buf.putInt(0);
+ } else {
+ buf.putInt(TimeUtil.dateToWenaTime(date3));
+ }
+ if(date4 == null) {
+ buf.putInt(0);
+ } else {
+ buf.putInt(TimeUtil.dateToWenaTime(date4));
+ }
+ return buf.array();
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTypeA.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTypeA.java
new file mode 100644
index 000000000..ff8c77643
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTypeA.java
@@ -0,0 +1,27 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
+
+import androidx.annotation.Nullable;
+
+import java.util.Date;
+
+public class ActivitySyncTimePacketTypeA extends ActivitySyncTimePacketTemplate {
+ public ActivitySyncTimePacketTypeA(@Nullable Date stepCountLastSyncTime, @Nullable Date heartRateLastSyncTime, @Nullable Date behaviorLastSyncTime, @Nullable Date vo2MaxLastSyncTime) {
+ super((byte) 0x1, stepCountLastSyncTime, heartRateLastSyncTime, behaviorLastSyncTime, vo2MaxLastSyncTime);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTypeB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTypeB.java
new file mode 100644
index 000000000..22c49781d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/ActivitySyncTimePacketTypeB.java
@@ -0,0 +1,27 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
+
+import androidx.annotation.Nullable;
+
+import java.util.Date;
+
+public class ActivitySyncTimePacketTypeB extends ActivitySyncTimePacketTemplate {
+ public ActivitySyncTimePacketTypeB(@Nullable Date stressLastSyncTime, @Nullable Date bodyEnergyLastSyncTime, @Nullable Date caloriesLastSyncTime, @Nullable Date eventsLastSyncTime) {
+ super((byte) 0x2, stressLastSyncTime, bodyEnergyLastSyncTime, caloriesLastSyncTime, eventsLastSyncTime);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/BehaviorSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/BehaviorSample.java
new file mode 100644
index 000000000..d986d8d03
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/BehaviorSample.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
+
+import java.util.Date;
+
+public class BehaviorSample {
+ public enum Type {
+ UNKNOWN,
+ NOT_WEARING,
+ WALK,
+ RUN,
+ EXERCISE,
+ SLEEP_DEEP,
+ SLEEP_LIGHT,
+ SLEEP_REM,
+ SLEEP_AWAKE,
+ STATIC;
+
+ public final static Type[] LUT = new Type[] {
+ UNKNOWN,
+ NOT_WEARING,
+ WALK,
+ RUN,
+ EXERCISE,
+ SLEEP_DEEP,
+ SLEEP_LIGHT,
+ SLEEP_REM,
+ SLEEP_AWAKE,
+ STATIC
+ };
+ }
+
+ public final Date start;
+ public final Date end;
+ public final Type type;
+
+ public BehaviorSample(Date start, Date end, Type type) {
+ this.start = start;
+ this.end = end;
+ this.type = type;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/Vo2MaxSample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/Vo2MaxSample.java
new file mode 100644
index 000000000..72ec25640
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/activity/Vo2MaxSample.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.activity;
+
+import java.util.Date;
+
+public class Vo2MaxSample {
+ public final Date timestamp;
+ public final int value;
+
+ public Vo2MaxSample(Date timestamp, int value) {
+ this.timestamp = timestamp;
+ this.value = value;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/calendar/CalendarEntry.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/calendar/CalendarEntry.java
new file mode 100644
index 000000000..4bfc7f4d9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/calendar/CalendarEntry.java
@@ -0,0 +1,81 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.calendar;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
+
+public class CalendarEntry implements Wena3Packetable {
+ public final Date begin;
+ public final Date end;
+ public final boolean isAllDay;
+ public final String title;
+ public final String location;
+ /// 1-based, not index!
+ public final byte position;
+ public final byte totalItemCount;
+
+ public CalendarEntry(Date begin, Date end, boolean isAllDay, String title, String location, byte position, byte totalItemCount) {
+ this.begin = begin;
+ this.end = end;
+ this.isAllDay = isAllDay;
+ this.title = title;
+ this.location = location;
+ this.position = position;
+ this.totalItemCount = totalItemCount;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ byte[] cstrTitle = title.getBytes(StandardCharsets.UTF_8);
+ byte[] cstrLocation = location.getBytes(StandardCharsets.UTF_8);
+ return ByteBuffer.allocate(14 + cstrTitle.length + cstrLocation.length)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x02)
+ .put(position)
+ .put(totalItemCount)
+ .put((byte) cstrTitle.length)
+ .put((byte) cstrLocation.length)
+ .put((byte) (isAllDay ? 0x1 : 0x0))
+ .putInt(TimeUtil.dateToWenaTime(begin))
+ .putInt(TimeUtil.dateToWenaTime(end))
+ .put(cstrTitle)
+ .put(cstrLocation)
+ .array();
+
+ }
+
+ public static byte[] byteArrayForEmptyEvent(byte position, byte totalItemCount) {
+ return ByteBuffer.allocate(14)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x02)
+ .put(position)
+ .put(totalItemCount)
+ .put((byte) 0x0)
+ .put((byte) 0x0)
+ .put((byte) 0x0)
+ .putInt(0)
+ .putInt(0)
+ .array();
+
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/EndOfIncomingCall.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/EndOfIncomingCall.java
new file mode 100644
index 000000000..ac641180b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/EndOfIncomingCall.java
@@ -0,0 +1,30 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+// NB: This seems to not work on the Wena 3!
+// For me, the vendor's app just sends an "APP" notification for incoming calls as well,
+// just with a continuous vibration...
+@Deprecated
+public class EndOfIncomingCall implements Wena3Packetable {
+ @Override
+ public byte[] toByteArray() {
+ return new byte[] { 0x1 };
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/NotificationArrival.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/NotificationArrival.java
new file mode 100644
index 000000000..7231cef3e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/NotificationArrival.java
@@ -0,0 +1,106 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.LedColor;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.NotificationFlags;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.NotificationKind;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.VibrationOptions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class NotificationArrival implements Wena3Packetable {
+ public final NotificationKind kind;
+ public final int id;
+ public final String title;
+ public final String message;
+ public final String actionLabel;
+ public final Date dateTime;
+ public final VibrationOptions vibration;
+ public final LedColor ledColor;
+ public final NotificationFlags flags;
+
+ public NotificationArrival(
+ NotificationKind kind,
+ int id,
+ String title,
+ String message,
+ String actionLabel,
+ Date dateTime,
+ VibrationOptions vibration,
+ LedColor ledColor,
+ NotificationFlags flags
+ ) {
+ this.kind = kind;
+ this.id = id;
+ this.title = title;
+ this.message = message;
+ this.actionLabel = actionLabel;
+ this.dateTime = dateTime;
+ this.vibration = vibration;
+ this.ledColor = ledColor;
+ this.flags = flags;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ assert vibration.count <= 255;
+
+ byte vibraFlags = 0;
+ byte vibraCount = (byte)vibration.count;
+
+ if(vibration.continuous) {
+ vibraFlags = 0x3;
+ vibraCount = 0;
+ } else {
+ if(vibraCount > 4) vibraCount = 4;
+ else if (vibraCount < 0) vibraCount = 1;
+ }
+
+ byte[] encodedTitle = StringUtils.truncate(title.trim(), 31).getBytes(StandardCharsets.UTF_8);
+ byte[] encodedMessage = StringUtils.truncate(message.trim(), 239).getBytes(StandardCharsets.UTF_8);
+ byte[] encodedAction = StringUtils.truncate(actionLabel.trim(), 15).getBytes(StandardCharsets.UTF_8);
+
+ ByteBuffer buf = ByteBuffer
+ .allocate(18 + encodedTitle.length + encodedMessage.length + encodedAction.length)
+ .order(ByteOrder.LITTLE_ENDIAN);
+
+ buf.put((byte) 0x00) // marker
+ .put((byte)ledColor.ordinal())
+ .put(vibraFlags)
+ .put((byte)vibration.kind.ordinal())
+ .put(vibraCount)
+ .put((byte)encodedTitle.length)
+ .put((byte)encodedMessage.length)
+ .put((byte)flags.value)
+ .put((byte)kind.ordinal())
+ .put((byte)encodedAction.length)
+ .putInt(id)
+ .putInt(TimeUtil.dateToWenaTime(dateTime))
+ .put(encodedTitle)
+ .put(encodedMessage)
+ .put(encodedAction);
+
+ return buf.array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/NotificationRemoval.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/NotificationRemoval.java
new file mode 100644
index 000000000..b8f38a68c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/NotificationRemoval.java
@@ -0,0 +1,44 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines.NotificationKind;
+
+public class NotificationRemoval implements Wena3Packetable {
+ public final NotificationKind kind;
+ public final int id;
+
+ public NotificationRemoval(NotificationKind kind, int id) {
+ this.kind = kind;
+ this.id = id;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer
+ .allocate(7)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x01)
+ .put((byte)kind.ordinal())
+ .putInt(id)
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/LedColor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/LedColor.java
new file mode 100644
index 000000000..f0df1b932
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/LedColor.java
@@ -0,0 +1,54 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
+
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
+
+public enum LedColor implements AbstractNotificationPattern {
+ NONE(0, R.string.prefs_wena3_led_none),
+ RED(1, R.string.red),
+ YELLOW(2, R.string.yellow),
+ GREEN(3, R.string.green),
+ CYAN(4, R.string.cyan),
+ BLUE(5, R.string.blue),
+ PURPLE(6, R.string.purple),
+ WHITE(7, R.string.white);
+
+ public final byte value;
+ private final int stringId;
+
+ LedColor(int value, int stringId) {
+ this.value = (byte) value;
+ this.stringId = stringId;
+ }
+
+
+ @Override
+ public String getUserReadableName(Context context) {
+ return context.getString(stringId);
+ }
+
+ @Override
+ public String getValue() {
+ return name();
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/NotificationFlags.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/NotificationFlags.java
new file mode 100644
index 000000000..c6b9ae49e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/NotificationFlags.java
@@ -0,0 +1,30 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
+
+public enum NotificationFlags {
+ NONE(0),
+ IS_TEST(1),
+ IS_RETRANSMISSION(2),
+ HAS_ACTION(4);
+
+ public int value = 0;
+ NotificationFlags(int value) {
+ this.value = value;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/NotificationKind.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/NotificationKind.java
new file mode 100644
index 000000000..774deceb0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/NotificationKind.java
@@ -0,0 +1,22 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
+
+public enum NotificationKind {
+ APP,
+ CALL
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationCount.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationCount.java
new file mode 100644
index 000000000..829434f3b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationCount.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 akasaka / Genjitsu Labs
+ *
+ * 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 .
+ */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
+
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
+
+public enum VibrationCount implements AbstractNotificationPattern {
+ INDEFINITE(0, R.string.prefs_wena3_notification_vibration_repetition_0),
+ ONCE(1, R.string.prefs_wena3_notification_vibration_repetition_1),
+ TWICE(2, R.string.prefs_wena3_notification_vibration_repetition_2),
+ THREE(3, R.string.prefs_wena3_notification_vibration_repetition_3),
+ FOUR(4, R.string.prefs_wena3_notification_vibration_repetition_4),
+ ;
+
+ public int value;
+ public int stringId;
+ VibrationCount(int value, int stringId) {
+ this.value = value;
+ this.stringId = stringId;
+ }
+
+
+ @Override
+ public String getUserReadableName(Context context) {
+ return context.getString(stringId);
+ }
+
+ @Override
+ public String getValue() {
+ return String.valueOf(value);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationKind.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationKind.java
new file mode 100644
index 000000000..0c9d910ed
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationKind.java
@@ -0,0 +1,56 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
+
+import android.content.Context;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
+
+public enum VibrationKind implements AbstractNotificationPattern {
+ NONE(0, R.string.prefs_wena3_vibration_none),
+ CONTINUOUS(1, R.string.prefs_wena3_vibration_continuous),
+ BASIC(2, R.string.prefs_wena3_vibration_basic),
+ RAPID(3, R.string.prefs_wena3_vibration_rapid),
+ TRIPLE(4, R.string.prefs_wena3_vibration_triple),
+ STEP_UP(5, R.string.prefs_wena3_vibration_step_up),
+ STEP_DOWN(6, R.string.prefs_wena3_vibration_step_down),
+ WARNING(7, R.string.prefs_wena3_vibration_warning),
+ SIREN(8, R.string.prefs_wena3_vibration_siren),
+ SHORT(9, R.string.prefs_wena3_vibration_short);
+
+ public final byte value;
+ private final int stringId;
+
+ VibrationKind(int value, int stringId) {
+ this.value = (byte) value;
+ this.stringId = stringId;
+ }
+
+
+ @Override
+ public String getUserReadableName(Context context) {
+ return context.getString(stringId);
+ }
+
+ @Override
+ public String getValue() {
+ return name();
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationOptions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationOptions.java
new file mode 100644
index 000000000..bb354c7d1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/notification/defines/VibrationOptions.java
@@ -0,0 +1,31 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.notification.defines;
+
+public class VibrationOptions {
+ public final VibrationKind kind;
+ public final int count;
+ public final boolean continuous;
+
+ public VibrationOptions(VibrationKind kind, int count, boolean continuous) {
+ this.kind = kind;
+ this.count = count;
+ this.continuous = continuous;
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/AlarmListSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/AlarmListSettings.java
new file mode 100644
index 000000000..3f6923fd0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/AlarmListSettings.java
@@ -0,0 +1,60 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class AlarmListSettings implements Wena3Packetable {
+ public static final int MAX_ALARMS_IN_PACKET = 3;
+
+ // While the Wena 3 has 9 alarms, the packet fits 3 at most
+ // So to update all alarms you need to send 3 packets
+ public final List alarms;
+ // Usually 0 for first packet, 3 for second, 6 for third
+ public final int alarmListOffset;
+
+ public AlarmListSettings(List alarms, int alarmListOffset) {
+ this.alarms = alarms;
+ this.alarmListOffset = alarmListOffset;
+
+ assert (this.alarmListOffset % 3) == 0;
+ assert this.alarms.size() <= MAX_ALARMS_IN_PACKET;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ ByteBuffer buf = ByteBuffer.allocate(19).order(ByteOrder.LITTLE_ENDIAN);
+
+ buf.put((byte) 0x06);
+
+ for(int i = 0; i < MAX_ALARMS_IN_PACKET; i++) {
+ buf.put((byte) (alarmListOffset + i));
+ if(i < alarms.size()) {
+ SingleAlarmSetting alarm = alarms.get(i);
+ buf.put(alarm.toByteArray());
+ } else {
+ buf.put(SingleAlarmSetting.emptyPacket());
+ }
+ }
+
+ return buf.array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/AutoPowerOffSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/AutoPowerOffSettings.java
new file mode 100644
index 000000000..667abc598
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/AutoPowerOffSettings.java
@@ -0,0 +1,40 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class AutoPowerOffSettings extends DoNotDisturbSettings {
+
+ public AutoPowerOffSettings(boolean enable, int startHour, int startMinute, int endHour, int endMinute) {
+ super(enable, startHour, startMinute, endHour, endMinute);
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer.allocate(6)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x15)
+ .put((byte) (enable ? 0x1 : 0x0))
+ .put((byte) startHour)
+ .put((byte) startMinute)
+ .put((byte) endHour)
+ .put((byte) endMinute)
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/BodyPropertiesSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/BodyPropertiesSetting.java
new file mode 100644
index 000000000..35edc2ba7
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/BodyPropertiesSetting.java
@@ -0,0 +1,56 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.GenderSetting;
+
+public class BodyPropertiesSetting implements Wena3Packetable {
+ public final GenderSetting gender;
+ public final short yearOfBirth;
+ public final short monthOfBirth;
+ public final short dayOfBirth;
+ public final short height;
+ public final short weight;
+
+ public BodyPropertiesSetting(GenderSetting gender, short yearOfBirth, short monthOfBirth, short dayOfBirth, short height, short weight) {
+ this.gender = gender;
+ this.yearOfBirth = yearOfBirth;
+ this.monthOfBirth = monthOfBirth;
+ this.dayOfBirth = dayOfBirth;
+ this.height = height;
+ this.weight = weight;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer.allocate(10)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x1D)
+ .put((byte) gender.ordinal())
+ .putShort(yearOfBirth)
+ .put((byte) (monthOfBirth + 1)) // Java uses 0-indexed months
+ .put((byte) dayOfBirth)
+ .putShort((short) (height * 10))
+ .putShort((short) (weight * 10))
+ .array();
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/CalendarNotificationEnableSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/CalendarNotificationEnableSetting.java
new file mode 100644
index 000000000..40aae9fb8
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/CalendarNotificationEnableSetting.java
@@ -0,0 +1,38 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class CalendarNotificationEnableSetting implements Wena3Packetable {
+ public final boolean enableCalendar;
+ public final boolean enableNotifications;
+
+ public CalendarNotificationEnableSetting(boolean enableCalendar, boolean enableNotifications) {
+ this.enableCalendar = enableCalendar;
+ this.enableNotifications = enableNotifications;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return new byte[] {
+ 0x12,
+ (byte) (enableNotifications ? 0x1 : 0x0),
+ (byte) (enableCalendar ? 0x1 : 0x0)
+ };
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/CameraAppTypeSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/CameraAppTypeSetting.java
new file mode 100644
index 000000000..b43b62c4b
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/CameraAppTypeSetting.java
@@ -0,0 +1,52 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import android.content.pm.PackageManager;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class CameraAppTypeSetting implements Wena3Packetable {
+ private static final String PHOTOPRO_APP_ID = "com.sonymobile.photopro";
+ public final boolean hasXperiaApp;
+
+ public CameraAppTypeSetting(boolean isXperia) {
+ this.hasXperiaApp = isXperia;
+ }
+
+ public static CameraAppTypeSetting findOut(PackageManager pm) {
+ try {
+ pm.getPackageInfo(PHOTOPRO_APP_ID, 0);
+ return new CameraAppTypeSetting(true);
+ } catch (PackageManager.NameNotFoundException e) {
+ return new CameraAppTypeSetting(false);
+ }
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer
+ .allocate(2)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte)0x21)
+ .put((byte) (hasXperiaApp ? 0x1 : 0x0))
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DayStartHourSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DayStartHourSetting.java
new file mode 100644
index 000000000..27dca57d0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DayStartHourSetting.java
@@ -0,0 +1,34 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class DayStartHourSetting implements Wena3Packetable {
+ public final int dayStartHour;
+
+ public DayStartHourSetting(int dayStartHour) {
+ this.dayStartHour = dayStartHour;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return new byte[] {
+ 0x16, (byte) dayStartHour
+ };
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DeviceButtonActionSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DeviceButtonActionSetting.java
new file mode 100644
index 000000000..49031412e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DeviceButtonActionSetting.java
@@ -0,0 +1,43 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.DeviceButtonActionId;
+
+public class DeviceButtonActionSetting implements Wena3Packetable {
+ public final DeviceButtonActionId longPress;
+ public final DeviceButtonActionId doubleClick;
+
+ public DeviceButtonActionSetting(DeviceButtonActionId longPress, DeviceButtonActionId doubleClick) {
+ this.longPress = longPress;
+ this.doubleClick = doubleClick;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer.allocate(5)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte)0x1C)
+ .putShort(longPress.value)
+ .putShort(doubleClick.value)
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DisplaySetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DisplaySetting.java
new file mode 100644
index 000000000..aa7b3b302
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DisplaySetting.java
@@ -0,0 +1,63 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.DisplayDesign;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.DisplayOrientation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.FontSize;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.Language;
+
+public class DisplaySetting implements Wena3Packetable {
+ public final boolean displayOnRaiseWrist;
+ public final Language language;
+ public final int displayDuration;
+ public final DisplayOrientation orientation;
+ public final DisplayDesign design;
+ public final FontSize fontSize;
+ public final boolean weatherInStatusBar;
+
+ public DisplaySetting(boolean displayOnRaiseWrist, Language language, int displayDuration, DisplayOrientation orientation, DisplayDesign design, FontSize fontSize, boolean weatherInStatusBar) {
+ this.displayOnRaiseWrist = displayOnRaiseWrist;
+ this.language = language;
+ this.displayDuration = displayDuration;
+ this.orientation = orientation;
+ this.design = design;
+ this.fontSize = fontSize;
+ this.weatherInStatusBar = weatherInStatusBar;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer
+ .allocate(8)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte)0x1A)
+ .put((byte) fontSize.ordinal())
+ .put((byte) orientation.ordinal())
+ .put((byte) language.ordinal())
+ .put((byte) (displayOnRaiseWrist ? 0x1 : 0x0))
+ .put((byte) design.ordinal())
+ .put((byte) (weatherInStatusBar ? 0x1 : 0x0))
+ .put((byte) displayDuration)
+ .array();
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DoNotDisturbSettings.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DoNotDisturbSettings.java
new file mode 100644
index 000000000..333fff085
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/DoNotDisturbSettings.java
@@ -0,0 +1,51 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class DoNotDisturbSettings implements Wena3Packetable {
+ public final boolean enable;
+ public final int startHour;
+ public final int startMinute;
+ public final int endHour;
+ public final int endMinute;
+
+ public DoNotDisturbSettings(boolean enable, int startHour, int startMinute, int endHour, int endMinute) {
+ this.enable = enable;
+ this.startHour = startHour;
+ this.startMinute = startMinute;
+ this.endHour = endHour;
+ this.endMinute = endMinute;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer.allocate(6)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x14)
+ .put((byte) (enable ? 0x1 : 0x0))
+ .put((byte) startHour)
+ .put((byte) startMinute)
+ .put((byte) endHour)
+ .put((byte) endMinute)
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/GoalStepsSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/GoalStepsSetting.java
new file mode 100644
index 000000000..9494dc8db
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/GoalStepsSetting.java
@@ -0,0 +1,42 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class GoalStepsSetting implements Wena3Packetable {
+ public final boolean goalNotificationEnabled;
+ public final int goalSteps;
+
+ public GoalStepsSetting(boolean goalNotificationEnabled, int goalSteps) {
+ this.goalNotificationEnabled = goalNotificationEnabled;
+ this.goalSteps = goalSteps;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer.allocate(6)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x17)
+ .put((byte) (goalNotificationEnabled ? 0x1 : 0x0))
+ .putInt(goalSteps)
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/HomeIconOrderSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/HomeIconOrderSetting.java
new file mode 100644
index 000000000..f7010304d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/HomeIconOrderSetting.java
@@ -0,0 +1,47 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.HomeIconId;
+
+public class HomeIconOrderSetting implements Wena3Packetable {
+ public final HomeIconId left;
+ public final HomeIconId center;
+ public final HomeIconId right;
+
+ public HomeIconOrderSetting(HomeIconId left, HomeIconId center, HomeIconId right) {
+ this.left = left;
+ this.center = center;
+ this.right = right;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer.allocate(7)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x1B)
+ .putShort(left.value)
+ .putShort(center.value)
+ .putShort(right.value)
+ .array();
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/MenuIconSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/MenuIconSetting.java
new file mode 100644
index 000000000..26ea4eea9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/MenuIconSetting.java
@@ -0,0 +1,42 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.MenuIconId;
+
+public class MenuIconSetting implements Wena3Packetable {
+ public final List iconList = new ArrayList<>();
+
+ public MenuIconSetting() {
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ ByteBuffer buf = ByteBuffer.allocate(1 + iconList.size()).order(ByteOrder.LITTLE_ENDIAN);
+ buf.put((byte) 0x19);
+ for(MenuIconId id : iconList) {
+ buf.put(id.value);
+ }
+ return buf.array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/SingleAlarmSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/SingleAlarmSetting.java
new file mode 100644
index 000000000..f18664b32
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/SingleAlarmSetting.java
@@ -0,0 +1,64 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class SingleAlarmSetting implements Wena3Packetable {
+ public final boolean enable;
+ // Bitmask: See model.Alarm.ALARM_MON, TUE, ...
+ public final byte repetition;
+ public final int smartAlarmMargin;
+ public final int hour;
+ public final int minute;
+
+ public SingleAlarmSetting(boolean enable, byte repetition, int smartAlarmMargin, int hour, int minute) {
+ this.enable = enable;
+ this.repetition = repetition;
+ this.smartAlarmMargin = smartAlarmMargin;
+ this.hour = hour;
+ this.minute = minute;
+ }
+
+ // NB: normally this never occurs on the wire
+ // outside of an AlarmListSettings packet!
+ @Override
+ public byte[] toByteArray() {
+ // For some reason their bitmask starts on Sunday!
+ // So this brings it in line with what Gadgetbridge expects...
+ byte newRepetition = (byte) ((((repetition & ~Alarm.ALARM_SUN) << 1) | ((repetition & Alarm.ALARM_SUN) >> 6)) & 0xFF);
+ return ByteBuffer.allocate(5)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) (enable ? 0x2 : 0x1)) // 0x0 means no object...
+ .put(newRepetition)
+ .put((byte) smartAlarmMargin)
+ .put((byte) hour)
+ .put((byte) minute)
+ .array();
+ }
+
+ public static byte[] emptyPacket() {
+ return ByteBuffer.allocate(5)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put(new byte[] {0, 0, 0, 0, 0})
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/StatusPageOrderSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/StatusPageOrderSetting.java
new file mode 100644
index 000000000..f0435c312
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/StatusPageOrderSetting.java
@@ -0,0 +1,41 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.StatusPageId;
+
+public class StatusPageOrderSetting implements Wena3Packetable {
+ public final List pages = new ArrayList<>();
+
+ public StatusPageOrderSetting() {}
+
+ @Override
+ public byte[] toByteArray() {
+ ByteBuffer buf = ByteBuffer.allocate(1 + pages.size()).order(ByteOrder.LITTLE_ENDIAN);
+ buf.put((byte) 0x18);
+ for(StatusPageId id : pages) {
+ buf.put(id.value);
+ }
+ return buf.array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/TimeSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/TimeSetting.java
new file mode 100644
index 000000000..ef694141e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/TimeSetting.java
@@ -0,0 +1,41 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util.TimeUtil;
+
+public class TimeSetting implements Wena3Packetable {
+ public final Date currentTime;
+
+ public TimeSetting(Date dateTime) {
+ this.currentTime = dateTime;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return ByteBuffer.allocate(5)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x10)
+ .putInt(TimeUtil.dateToWenaTime(currentTime))
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/TimeZoneSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/TimeZoneSetting.java
new file mode 100644
index 000000000..3baa067d4
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/TimeZoneSetting.java
@@ -0,0 +1,46 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+import java.util.TimeZone;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class TimeZoneSetting implements Wena3Packetable {
+ public final TimeZone timeZone;
+ public final Date referenceDate;
+
+ public TimeZoneSetting(TimeZone tz, Date referenceDate) {
+ this.timeZone = tz;
+ this.referenceDate = referenceDate;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ int offset = timeZone.getOffset(referenceDate.getTime());
+ return ByteBuffer
+ .allocate(3)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte)0x11)
+ .put((byte) (offset / 3_600_000))
+ .put((byte) ((offset / 60_000) % 60))
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/VibrationSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/VibrationSetting.java
new file mode 100644
index 000000000..ec11dc431
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/VibrationSetting.java
@@ -0,0 +1,40 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines.VibrationStrength;
+
+public class VibrationSetting implements Wena3Packetable {
+ public final boolean smartVibration;
+ public final VibrationStrength strength;
+
+ public VibrationSetting(boolean smartVibration, VibrationStrength strength) {
+ this.smartVibration = smartVibration;
+ this.strength = strength;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ return new byte[] {
+ 0x08,
+ (byte) strength.ordinal(),
+ (byte) (smartVibration ? 0x01 : 0x00)
+ };
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DeviceButtonActionId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DeviceButtonActionId.java
new file mode 100644
index 000000000..767735b52
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DeviceButtonActionId.java
@@ -0,0 +1,50 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+public enum DeviceButtonActionId {
+ NONE(0),
+ ALEXA(1),
+ QRIO(2),
+ QRIO_UNLOCK(258),
+ QRIO_LOCK(514),
+ FIND_PHONE(3),
+ TOGGLE_NFC(4),
+ TOGGLE_SILENT(5),
+ START_TIMER(262),
+ TIMER(6),
+ WEATHER(7),
+ WENA_PAY(8),
+ SCHEDULE(9),
+ ACTIVITY_SCREEN(10),
+ NOTIFICATION_SCREEN(11),
+ SUICA(12),
+ EDY(13),
+ ALARM(14),
+ RIIIVER(15),
+ MUSIC(16),
+ TOGGLE_MUSIC(272),
+ MUSIC_NEXT(528),
+ MUSIC_PREV(784);
+
+
+ public final short value;
+
+ DeviceButtonActionId(int val) {
+ this.value = (short) val;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DisplayDesign.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DisplayDesign.java
new file mode 100644
index 000000000..c81e905d0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DisplayDesign.java
@@ -0,0 +1,22 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+public enum DisplayDesign {
+ NORMAL,
+ RICH
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DisplayOrientation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DisplayOrientation.java
new file mode 100644
index 000000000..4f4cb7952
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/DisplayOrientation.java
@@ -0,0 +1,22 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+public enum DisplayOrientation {
+ RIGHT_HAND,
+ LEFT_HAND
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/FontSize.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/FontSize.java
new file mode 100644
index 000000000..6d6da1245
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/FontSize.java
@@ -0,0 +1,22 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+public enum FontSize {
+ NORMAL,
+ LARGE
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/GenderSetting.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/GenderSetting.java
new file mode 100644
index 000000000..9e24eaf75
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/GenderSetting.java
@@ -0,0 +1,22 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+public enum GenderSetting {
+ FEMALE, // == 0
+ MALE // == 1
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/HomeIconId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/HomeIconId.java
new file mode 100644
index 000000000..c15c9fb31
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/HomeIconId.java
@@ -0,0 +1,50 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+// This is done via a class to be able to init the value from an arbitrary int (e.g. from prefs)
+// Whether listing all cases is needed is a good concern, they are essentially unused because
+// the settings dialog uses a string-array instead...
+public enum HomeIconId {
+ TIMER(256),
+ ALARM(512),
+ CLOCK(768),
+ ALEXA(1024),
+ WENA_PAY(1280),
+ QRIO_LOCK(1536),
+ EDY(1792),
+ NOTIFICATION_COUNT(2048),
+ SCHEDULE(2304),
+ PEDOMETER(2560),
+ SLEEP(2816),
+ HEART_RATE(3072),
+ VO2MAX(3328),
+ STRESS(3584),
+ ENERGY(3840),
+
+ SUICA(4096),
+ CALORIES(4352),
+ RIIIVER(4608),
+ MUSIC(4864),
+ CAMERA(5120);
+
+ public final short value;
+
+ HomeIconId(final int value) {
+ this.value = (short) value;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/Language.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/Language.java
new file mode 100644
index 000000000..f5ea8b502
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/Language.java
@@ -0,0 +1,22 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+public enum Language {
+ ENGLISH,
+ JAPANESE
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/MenuIconId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/MenuIconId.java
new file mode 100644
index 000000000..f85e73d0e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/MenuIconId.java
@@ -0,0 +1,36 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+public enum MenuIconId {
+ NONE(0),
+ TIMER(1),
+ ALARM(2),
+ FIND_PHONE(3),
+ ALEXA(4),
+ PAYMENT(5),
+ QRIO(6),
+ WEATHER(7),
+ MUSIC(8),
+ CAMERA(9);
+
+ public final byte value;
+
+ MenuIconId(int value) {
+ this.value = (byte) value;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/StatusPageId.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/StatusPageId.java
new file mode 100644
index 000000000..eb851996d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/StatusPageId.java
@@ -0,0 +1,34 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+public enum StatusPageId {
+ NONE(0),
+ PEDOMETER(1),
+ SLEEP(2),
+ HEART_RATE(3),
+ VO2MAX(4),
+ STRESS(5),
+ ENERGY(6),
+ CALORIES(7);
+
+ public final byte value;
+
+ StatusPageId(int val) {
+ this.value = (byte) val;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/VibrationStrength.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/VibrationStrength.java
new file mode 100644
index 000000000..d93276d3c
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/settings/defines/VibrationStrength.java
@@ -0,0 +1,29 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.settings.defines;
+
+public enum VibrationStrength {
+ NORMAL(0),
+ WEAK(1),
+ STRONG(2);
+
+ public final byte value;
+
+ VibrationStrength(int val) {
+ this.value = (byte) val;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/BatteryLevelInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/BatteryLevelInfo.java
new file mode 100644
index 000000000..7883ef5b0
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/BatteryLevelInfo.java
@@ -0,0 +1,33 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
+
+public class BatteryLevelInfo {
+ public final int batteryPercentage;
+
+ public BatteryLevelInfo(byte[] packet) {
+ this.batteryPercentage = Integer.valueOf(packet[0]);
+ }
+
+ public GBDeviceEventBatteryInfo toDeviceEvent() {
+ final GBDeviceEventBatteryInfo deviceEventBatteryInfo = new GBDeviceEventBatteryInfo();
+ deviceEventBatteryInfo.level = batteryPercentage;
+ return deviceEventBatteryInfo;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/DeviceInfo.java
new file mode 100644
index 000000000..86a88b1dd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/DeviceInfo.java
@@ -0,0 +1,43 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
+
+public class DeviceInfo {
+ public final String firmwareName;
+ public final String serialNo;
+
+ public DeviceInfo(byte[] packet) {
+ ByteBuffer buf = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
+ byte[] fwNameStr = new byte[8];
+ buf.get(fwNameStr);
+ serialNo = Integer.toString(buf.getInt());
+ firmwareName = new String(fwNameStr, StandardCharsets.UTF_8);
+ }
+
+ public GBDeviceEventVersionInfo toDeviceEvent() {
+ final GBDeviceEventVersionInfo gbDeviceEventVersionInfo = new GBDeviceEventVersionInfo();
+ gbDeviceEventVersionInfo.fwVersion = firmwareName;
+ gbDeviceEventVersionInfo.fwVersion2 = serialNo;
+ return gbDeviceEventVersionInfo;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/MusicInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/MusicInfo.java
new file mode 100644
index 000000000..fb8ba4fbd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/MusicInfo.java
@@ -0,0 +1,44 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
+
+public class MusicInfo implements Wena3Packetable {
+ public final String musicInfoText;
+
+ public MusicInfo(String musicInfoText) {
+ this.musicInfoText = musicInfoText;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ byte[] encodedString = StringUtils.truncate(musicInfoText.trim(), 99).getBytes(StandardCharsets.UTF_8);
+ return ByteBuffer
+ .allocate(2 + encodedString.length)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte)0x05)
+ .put((byte)encodedString.length)
+ .put(encodedString)
+ .array();
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/NotificationServiceStatusRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/NotificationServiceStatusRequest.java
new file mode 100644
index 000000000..9e0dffd03
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/NotificationServiceStatusRequest.java
@@ -0,0 +1,26 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
+
+public class NotificationServiceStatusRequest {
+ public final int requestType;
+ public int unknown;
+
+ public NotificationServiceStatusRequest(byte[] packet) {
+ this.requestType = Integer.valueOf(packet[0]);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/StatusRequestType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/StatusRequestType.java
new file mode 100644
index 000000000..4e43cfa60
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/StatusRequestType.java
@@ -0,0 +1,34 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
+
+public enum StatusRequestType {
+ BACKGROUND_SYNC_REQUEST(0), // timer?
+ GET_CALENDAR(1),
+ LOCATE_PHONE(2),
+ GET_WEATHER(3),
+ RIIIVER_SIDE_BUTTON_ENGAGED(4), // -> 0x0 double click, 0x1 long click
+ RIIIVER_HOME_ICON_CLICKED(5), // -> 0x00 0x12, 0x01 0x12, 0x02 0x12 : button no.?
+ UNKNOWN1(6), // -> payment related?
+ NOTIFICATION_REMOVE_REQUEST(10), // -> followed by int notification id
+ MUSIC_INFO_FETCH(11);
+
+ public final int value;
+ StatusRequestType(int val) {
+ this.value = val;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/Weather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/Weather.java
new file mode 100644
index 000000000..b30374bfd
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/Weather.java
@@ -0,0 +1,135 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
+
+import org.slf4j.LoggerFactory;
+
+public class Weather {
+ private final WeatherType main;
+ private final WeatherType sub;
+
+ public Weather(WeatherType main, WeatherType sub) {
+ this.main = main;
+ this.sub = sub;
+ }
+
+ public byte packed() {
+ return (byte)((main.ordinal() << 3) | sub.ordinal());
+ }
+
+ public static Weather fromOpenWeatherMap(int conditionCode) {
+ WeatherType main = WeatherType.SUNNY;
+ WeatherType sub = WeatherType.SUNNY;
+
+ switch (conditionCode) {
+ // https://openweathermap.org/weather-conditions
+ case 200:
+ case 201:
+ case 210:
+ case 230:
+ case 231:
+ case 300:
+ case 301:
+ case 310:
+ case 311:
+ case 313:
+ case 500:
+ case 501:
+ case 520:
+ case 521:
+ main = WeatherType.RAIN;
+ sub = WeatherType.RAIN;
+ break;
+
+ case 202:
+ case 212:
+ case 221:
+ case 232:
+ case 302:
+ case 312:
+ case 314:
+ case 321:
+ case 502:
+ case 503:
+ case 504:
+ case 522:
+ case 531:
+ main = WeatherType.HEAVY_RAIN;
+ sub = WeatherType.HEAVY_RAIN;
+ break;
+
+ case 511:
+ case 615:
+ case 616:
+ main = WeatherType.RAIN;
+ sub = WeatherType.SNOW;
+ break;
+
+ case 600:
+ case 601:
+ case 602:
+ case 611:
+ case 612:
+ case 613:
+ case 620:
+ case 621:
+ case 622:
+ main = WeatherType.SNOW;
+ sub = WeatherType.SNOW;
+ break;
+
+ case 701:
+ case 711:
+ case 721:
+ case 731:
+ case 741:
+ case 751:
+ case 761:
+ case 762:
+ case 771:
+ case 781:
+ main = WeatherType.SUNNY;
+ sub = WeatherType.CLOUDY;
+ break;
+
+ case 800:
+ case 801:
+ main = WeatherType.SUNNY;
+ sub = WeatherType.SUNNY;
+ break;
+
+ case 802:
+ case 803:
+ main = WeatherType.SUNNY;
+ sub = WeatherType.CLOUDY;
+ break;
+
+ case 804:
+ main = WeatherType.CLOUDY;
+ sub = WeatherType.CLOUDY;
+ break;
+
+ default:
+ LoggerFactory.getLogger(Weather.class)
+ .warn("Unknown condition code " + conditionCode);
+ break;
+ }
+
+ return new Weather(main, sub);
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherDay.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherDay.java
new file mode 100644
index 000000000..7df8f7114
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherDay.java
@@ -0,0 +1,46 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
+
+import lineageos.weather.util.WeatherUtils;
+import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
+
+public class WeatherDay {
+ public final Weather day;
+ public final Weather night;
+ public final int temperatureMaximum;
+ public final int temperatureMinimum;
+
+ public WeatherDay(Weather day, Weather night, int temperatureMaximum, int temperatureMinimum) {
+ this.day = day;
+ this.night = night;
+ // For some reason, Wena uses Farenheit on the wire, but Celsius on display...
+ // Assume a middle ground input in Kelvin.
+ this.temperatureMaximum = (int) Math.round(WeatherUtils.celsiusToFahrenheit(temperatureMaximum - 273.15));
+ this.temperatureMinimum = (int) Math.round(WeatherUtils.celsiusToFahrenheit(temperatureMinimum - 273.15));
+ }
+
+ public static WeatherDay fromSpec(WeatherSpec.Daily daily) {
+ return new WeatherDay(
+ Weather.fromOpenWeatherMap(daily.conditionCode),
+ Weather.fromOpenWeatherMap(daily.conditionCode),
+ daily.maxTemp,
+ daily.minTemp
+ );
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherReport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherReport.java
new file mode 100644
index 000000000..f4c4b26a9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherReport.java
@@ -0,0 +1,51 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.Wena3Packetable;
+
+public class WeatherReport implements Wena3Packetable {
+ public final List fiveDays;
+
+ public WeatherReport(List fiveDays) {
+ this.fiveDays = fiveDays;
+ assert this.fiveDays.size() == 5;
+ }
+
+ @Override
+ public byte[] toByteArray() {
+ ByteBuffer buf = ByteBuffer
+ .allocate(21)
+ .order(ByteOrder.LITTLE_ENDIAN)
+ .put((byte) 0x03);
+
+ for(int i = 0; i < 5; i++) {
+ WeatherDay current = this.fiveDays.get(i);
+ buf.put(current.day.packed());
+ buf.put(current.night.packed());
+ buf.put((byte) (current.temperatureMinimum + 100));
+ buf.put((byte) (current.temperatureMaximum + 100));
+ }
+
+ return buf.array();
+ }
+}
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherType.java
new file mode 100644
index 000000000..38d66b64d
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/packets/status/WeatherType.java
@@ -0,0 +1,25 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.packets.status;
+
+public enum WeatherType {
+ SUNNY,
+ CLOUDY,
+ RAIN,
+ HEAVY_RAIN,
+ SNOW
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/util/TimeUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/util/TimeUtil.java
new file mode 100644
index 000000000..d942b7acc
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/wena3/protocol/util/TimeUtil.java
@@ -0,0 +1,31 @@
+/* Copyright (C) 2023 akasaka / Genjitsu Labs
+
+ 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 . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.wena3.protocol.util;
+
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Constants;
+
+public class TimeUtil {
+ public static int dateToWenaTime(Date date) {
+ return (int) ((date.getTime() - SonyWena3Constants.EPOCH_START) / (long)1000);
+ }
+
+ public static Date wenaTimeToDate(int wenaTime) {
+ return new Date(((long) wenaTime) * 1000L + SonyWena3Constants.EPOCH_START);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
index d7863207e..dfbc1213e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java
@@ -52,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.BohemicSmartBraceletDe
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyLinkBudsSCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.soflow.SoFlowCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWH1000XM5Coordinator;
+import nodomain.freeyourgadget.gadgetbridge.devices.sony.wena3.SonyWena3Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.supercars.SuperCarsCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
@@ -390,6 +391,7 @@ public class DeviceHelper {
result.add(new SonyWH1000XM2Coordinator());
result.add(new SonyWF1000XM4Coordinator());
result.add(new SonyWH1000XM5Coordinator());
+ result.add(new SonyWena3Coordinator());
result.add(new QC35Coordinator());
result.add(new BinarySensorCoordinator());
result.add(new FlipperZeroCoordinator());
diff --git a/app/src/main/res/drawable/ic_wb_twilight.xml b/app/src/main/res/drawable/ic_wb_twilight.xml
new file mode 100644
index 000000000..e9d44828e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wb_twilight.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_wena_icon_center.xml b/app/src/main/res/drawable/ic_wena_icon_center.xml
new file mode 100644
index 000000000..9a66b9a90
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wena_icon_center.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_wena_icon_left.xml b/app/src/main/res/drawable/ic_wena_icon_left.xml
new file mode 100644
index 000000000..d5383e587
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wena_icon_left.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_wena_icon_right.xml b/app/src/main/res/drawable/ic_wena_icon_right.xml
new file mode 100644
index 000000000..712588618
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wena_icon_right.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_app_specific_notification_settings.xml b/app/src/main/res/layout/activity_app_specific_notification_settings.xml
new file mode 100644
index 000000000..64955fb64
--- /dev/null
+++ b/app/src/main/res/layout/activity_app_specific_notification_settings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_notification_per_app_setting_detail.xml b/app/src/main/res/layout/activity_notification_per_app_setting_detail.xml
new file mode 100644
index 000000000..875644d00
--- /dev/null
+++ b/app/src/main/res/layout/activity_notification_per_app_setting_detail.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_app_specific_notification_app_list.xml b/app/src/main/res/layout/item_app_specific_notification_app_list.xml
new file mode 100644
index 000000000..73ad6499d
--- /dev/null
+++ b/app/src/main/res/layout/item_app_specific_notification_app_list.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 1ea357cc6..76a3dc6dd 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -3114,4 +3114,284 @@
+
+
+ @string/prefs_wena3_vibration_strength_item_weak
+ @string/prefs_wena3_vibration_strength_item_medium
+ @string/prefs_wena3_vibration_strength_item_strong
+
+
+ weak
+ normal
+ strong
+
+
+
+ @string/menuitem_timer
+ @string/menuitem_alarm
+ @string/prefs_wena3_home_icon_name_clock
+
+
+
+ @string/prefs_wena3_home_icon_name_edy
+ @string/menuitem_notifications
+ @string/menuitem_events
+ @string/prefs_wena3_home_icon_name_pedometer
+ @string/menuitem_sleep
+ @string/menuitem_hr
+ @string/menuitem_vo2_max
+ @string/menuitem_stress
+ @string/prefs_wena3_home_icon_name_energy
+ @string/prefs_wena3_home_icon_name_suica
+ @string/prefs_wena3_home_icon_name_calories
+
+ @string/menuitem_music
+ @string/menuitem_takephoto
+
+
+ timer
+ alarm
+ clock
+
+
+
+ edy
+ notification_count
+ schedule
+ pedometer
+ sleep
+ heart_rate
+ vo2max
+ stress
+ energy
+ suica
+ calories
+
+ music
+ camera
+
+
+
+ @string/menuitem_timer
+ @string/menuitem_alarm
+ @string/menuitem_findphone
+
+ @string/prefs_wena3_menu_icon_name_payment
+
+ @string/menuitem_weather
+ @string/menuitem_music
+ @string/menuitem_takephoto
+
+
+ timer
+ alarm
+ find_phone
+
+ payment
+
+ weather
+ music
+ camera
+
+
+ alarm
+ find_phone
+ weather
+ camera
+ music
+ payment
+
+
+
+ @string/prefs_wena3_home_icon_name_pedometer
+ @string/menuitem_sleep
+ @string/menuitem_hr
+ @string/menuitem_vo2_max
+ @string/menuitem_stress
+ @string/prefs_wena3_home_icon_name_energy
+ @string/prefs_wena3_home_icon_name_calories
+
+
+ pedometer
+ sleep
+ heart_rate
+ vo2max
+ stress
+ energy
+ calories
+
+
+ pedometer
+ sleep
+ heart_rate
+ vo2max
+ stress
+ energy
+ calories
+
+
+
+ 00:00
+ 01:00
+ 02:00
+ 03:00
+ 04:00
+ 05:00
+ 06:00
+ 07:00
+ 08:00
+ 09:00
+ 10:00
+ 11:00
+ 12:00
+ 13:00
+ 14:00
+ 15:00
+ 16:00
+ 17:00
+ 18:00
+ 19:00
+ 20:00
+ 21:00
+ 22:00
+ 23:00
+
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+
+
+
+ @string/menuitem_nothing
+
+
+
+
+ @string/menuitem_findphone
+ @string/menuitem_nfc
+ @string/menuitem_dnd
+ @string/prefs_wena3_button_action_name_start_timer
+ @string/menuitem_timer
+ @string/menuitem_weather
+ @string/menuitem_events
+ @string/prefs_wena3_button_action_name_activity_screen
+ @string/menuitem_notifications
+ @string/prefs_wena3_home_icon_name_suica
+ @string/prefs_wena3_home_icon_name_edy
+ @string/menuitem_alarm
+
+ @string/menuitem_music
+ @string/pref_media_playpause
+ @string/pref_media_next
+ @string/pref_media_previous
+
+
+
+ none
+
+
+
+
+ find_phone
+ toggle_nfc
+ toggle_silent
+ start_timer
+ timer
+ weather
+ schedule
+ activity_screen
+ notification_screen
+ suica
+ edy
+ alarm
+
+ music
+ toggle_music
+ music_next
+ music_prev
+
+
+
+
+ @string/prefs_wena3_notification_vibration_repetition_0
+ @string/prefs_wena3_notification_vibration_repetition_1
+ @string/prefs_wena3_notification_vibration_repetition_2
+ @string/prefs_wena3_notification_vibration_repetition_3
+ @string/prefs_wena3_notification_vibration_repetition_4
+
+
+ 0
+ 1
+ 2
+ 3
+ 4
+
+
+
+ @string/prefs_wena3_vibration_none
+ @string/prefs_wena3_vibration_continuous
+ @string/prefs_wena3_vibration_basic
+ @string/prefs_wena3_vibration_rapid
+ @string/prefs_wena3_vibration_triple
+ @string/prefs_wena3_vibration_step_up
+ @string/prefs_wena3_vibration_step_down
+ @string/prefs_wena3_vibration_warning
+ @string/prefs_wena3_vibration_siren
+ @string/prefs_wena3_vibration_short
+
+
+ none
+ continuous
+ basic
+ rapid
+ triple
+ step_up
+ step_down
+ warning
+ siren
+ short
+
+
+
+ @string/prefs_wena3_led_none
+ @string/red
+ @string/yellow
+ @string/green
+ @string/cyan
+ @string/blue
+ @string/purple
+ @string/white
+
+
+ none
+ red
+ yellow
+ green
+ cyan
+ blue
+ purple
+ white
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c190d08e1..dce2225c8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2090,6 +2090,7 @@
On-device pairing confirmations can get annoying. Disabling them might lose you functionality.VESCBose QC35
+ Sony Wena 3Withings Steel HRDisabledMedia Play
@@ -2266,4 +2267,88 @@
Menu structure: %sPlease rebuild your watchface for custom menuMi Band HRX
+
+ Left
+ Center
+ Right
+
+ Red
+ Yellow
+ Green
+ Cyan
+ Blue
+ Purple
+ White
+
+ Display Settings
+ Activity Settings
+ Background Activity Data Sync
+ Allow the Wena to periodically ask Gadgetbridge to download activity data from the device
+ Turn on the display when you look at your wrist
+ Larger Font Size
+ Increase the font size in calendar, notifications, etc.
+ Use Rich Design
+ Adds rounded rectangles around home screen icons
+ Weather In Status Bar
+ Show current conditions icon in the top left corner of the home screen
+ Alarm Settings
+ Smart Alarm Margin
+ Vibration Strength
+ Smart Vibration
+ Weak
+ Medium
+ Strong
+ Home Screen Icons
+ Current Time
+ Wena Pay
+ Qrio
+ Edy balance
+ Step Count
+ Body Energy
+ Suica Balance
+ Calories
+ Riiiver
+ Payment
+ Menu Icons
+ The Settings icon is always shown at the end
+ Status Page Ordering
+ Day Starts At
+ Auto Power Off
+ The device will turn off and on automatically at the specified schedule
+ Shutdown time
+ Resume time
+ Button Action
+ Double Press
+ Long Press
+ Qrio Unlock
+ Qrio Lock
+ Start active timer
+ Activity Screen
+ Notification Settings
+ Incoming Call Settings
+ Receive Call Notifications
+ If turned off, you will not get notified of incoming calls on the Wena
+ Notification Vibration
+ Notification Vibration Repeat
+ Indefinitely
+ Once
+ Twice
+ 3 times
+ 4 times
+ Per-app Notification Settings
+ Notification LED Color
+ Incoming Call Vibration
+ Incoming Call Vibration Repeat
+ Incoming Call LED Color
+ No Vibration
+ Continuous
+ Basic
+ Rapid
+ Triple
+ Step Up
+ Step Down
+ Warning
+ Siren
+ Short
+ No LED
diff --git a/app/src/main/res/xml/devicesettings_wena3.xml b/app/src/main/res/xml/devicesettings_wena3.xml
new file mode 100644
index 000000000..549a3b20c
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_wena3.xml
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/devicesettings_wena3_auto_power_off.xml b/app/src/main/res/xml/devicesettings_wena3_auto_power_off.xml
new file mode 100644
index 000000000..a966fdfe4
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_wena3_auto_power_off.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+