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 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 getPairingActivity() { + return null; + } + + @Override + public String getManufacturer() { + return "Sony"; + } + + @Override + public boolean supportsAppsManagement(GBDevice device) { + return false; + } + + @Override + public Class getAppsManagementActivity() { + return null; + } + + @NonNull + @Override + public Class getDeviceSupportClass() { + return SonyWena3DeviceSupport.class; + } + + @Override + public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(GBDevice device) { + return new SonyWena3SettingsCustomizer(); + } + + @NonNull + @Override + public Collection 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 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 getSampleProvider(GBDevice device, DaoSession session) { + return new SonyWena3ActivitySampleProvider(device, session); + } + + @Override + public TimeSampleProvider 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 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 @@ + + + + + + + + + + + + + +